alloc.init vs new/initialize

1,321 views
Skip to first unread message

Rich

unread,
Jan 21, 2013, 1:34:57 AM1/21/13
to rubym...@googlegroups.com
Can anyone provide information on the way Ruby's new() and initialize() methods interact with Objective-C's initialization chain?

I'm a long-time Objective-C programmer, so I'm very familiar with the initialization chain, including how to override an init* method, and how to create custom init* methods. I'm also familiar with ruby's initialize() method. What I don't know is how the two interact.

The basic rule seems to be, if you're creating a Ruby object (e.g. Array), use Array.new(). If you're creating an Objective-C object (e.g. UIView) use an objective-c init method UIView.alloc.initWithFrame(frame);

But what about subclasses. If I'm creating a custom subclass of UIView and I want to create a custom initializer, should I create a custom init* method to go with it, or should I perform my custom initialization in the initialize() method. Honestly, I'm not sure how to do all the steps that Apple recommends for a proper init method in RubyMotion (e.g. calling the superclass's designated init method and assigning the value to self), so initialize seems the right way to go. But, how do I get this to call the superclass's designated initializer?

Also, what happens if I mix my metaphors: Array.alloc.init(), UIView.new(), etc.

I guess I'm looking for a pointer towards best practices. I'd also appreciate some under-the-hood info about how and where Ruby's initialize() method connects to the underlying Objective-C initialization chain. I was hoping for a relatively straight-forward connection (e.g. calling new() always called alloc.init() and then called initialize()--or something like that), but when I created some test code to trace through it, it seemed a lot more complex and varied than this.

Any suggestions?

-Rich-




Thomas Kadauke

unread,
Jan 21, 2013, 6:04:26 AM1/21/13
to rubym...@googlegroups.com
From what I understand, these are the rules:

- Use .alloc.init on Obj-C classes and subclasses
- Use .new on Ruby classes and subclasses
- .new calls initialize internally. I guess before that it calls .alloc.
- Using .new on Obj-C classes is bad. Try it. You won't like the results
- If you want to subclass an Obj-C class and call .new on it, make sure you call "init" from your "initialize" method. However, I'd think mixing the two initialization methods would be confusing to readers of your code.

--Thomas
> --
>
>
>

Rich

unread,
Jan 21, 2013, 10:52:18 AM1/21/13
to rubym...@googlegroups.com, thomas....@googlemail.com
Thomas,

Thank you for the reply.

I guess I'm really looking for more in-depth information on what's going on under the hood. Partially so I can make sure I'm using things correctly, but also partially so I can reassure myself that the system is working properly.

Here's the issue that confuses me:

In Objective-C init, correctly designed init methods have to do several tasks. The basic idea is that we want to guarantee that no matter which version of init we call, the call will be routed to our class's designated initializer (possibly with some default values passed in). It then gets handed to the superclass's designated initializer--and so forth up the chain. The end result is, at every level of the class hierarchy, the designated initializer is called once and only once.

So, let's say I call UIView.new(). 

1) This calles UIView.alloc.init()
2) This calls initWithFrame()  (UIView's Designated Initializer) and passes in CGRectZero as the default value.
3) This calls NSObject's init() (NSObject's Designated Initializer).
4) Everything is set up and ready to go.

Somewhat oddly to my mind, initialize() never seems to be called.

This raises some questions:

1) If I want to create a UIView subclass with a custom initialization method, initWithTitle(), how would I do that?

In Objective-C, I would have a number of steps I need to perform. In initWithTitle, I would need to call UIView's initWithFrame, and I would need to assign the returned value to self. I would also need to override initWithFrame, and have it call initWithTitle, passing in a default value for the title. I can't seem to do the first part, since I don't know how to call an arbitrary superclass method, and I don't think we can reassign self.

2) If I wanted to make a more Rubyesque wrapper around UIView, that used new(frame), and whose subclasses used initialize(), how would I implement this so that it correctly linked into the existing Objective-C. It seems like I should be able to override initialize and call my designated initializer inside there--but that doesn't seem to work. In my testing, my initialize() method is never called. 

Thanks,

-Rich-

Colin Thomas Arnold Gray

unread,
Jan 21, 2013, 3:26:44 PM1/21/13
to rubym...@googlegroups.com
First, I need to correct one thing that Thomas said.  This will also help lay out a path to accomplish what you want.

> Using .new on Obj-C classes is bad. Try it. You won't like the results

This is not true, NSObject defines a class method 'new' that calls '[[self alloc] init]', and this is also why defining 'initialize' in a subclass of a Cocoa class will not get called.

If you want to change the 'designated initializer', that's fine, and you should override `init` for that purpose.  Then, in initWithTitle, you call initWithFrame, the designated initializer of the parent class.  The only other use-case to keep in mind is when using a UIView from a NIB/XIB file, in which case you should implement `awakeFromNib` and set the title there.

You could go as far as implementing a class-method `new(title)`.  Nothing *stopping* you, though that does enter the realm of whether you do things "the ruby way" or "the cocoa way".  Your call!


Overriding initWithFrame to set the title as a backup seems like overkill to me, and strictly speaking it violates the 'designated initializer' pattern.  Who are you expecting to use your class that won't use it correctly?
--
 
 
 

Rich

unread,
Jan 22, 2013, 12:55:14 PM1/22/13
to rubym...@googlegroups.com
I've added comments inline below:


On Monday, January 21, 2013 2:26:44 PM UTC-6, colinta wrote:
First, I need to correct one thing that Thomas said.  This will also help lay out a path to accomplish what you want.

> Using .new on Obj-C classes is bad. Try it. You won't like the results

This is not true, NSObject defines a class method 'new' that calls '[[self alloc] init]', and this is also why defining 'initialize' in a subclass of a Cocoa class will not get called.

Drat, I alway forget about Objective-C's new method. OK, so this makes sense. UIView.new is calling Objective-C's new method, not Ruby's new method. I did a few other tests, and it looks like calling Array.new never calls NSObject's init method. So it looks like the two initialization paths are kept completely separate.

Which raises the question, when I'm creating a class, how do I pick which initialization pathway it should use? Is there a proper way to create Ruby-style objects vs. Objective-C style objects? I can see a possible need for creating both. For example, I might want to create wrappers around Objective-C objects to make them more Ruby-like. On the other hand, I might want to make sure my class has proper Objective-C init methods, so I can more easily use it in a static library outside of RubyMotion.

For example, I can create an Array using either Array.new or Array.alloc.init. The first one does not call NSObject's init method. The second one does. Both return an empty Array. I also get the same exact results using NSArray. 

Also, what happens if Apple changes their underlying implementation (e.g. adds a feature that depends on NSObject's init method being called). I assume they'll fix RubyMotion to match--but how do I make sure my objects get updated as well? In other words, my Ruby-style objects must be implemented using the same technique as RubyMotion's Ruby objects, so they can also benefit from any updates and bug fixes made to RubyMotion.

If you want to change the 'designated initializer', that's fine, and you should override `init` for that purpose.  Then, in initWithTitle, you call initWithFrame, the designated initializer of the parent class ... Overriding initWithFrame to set the title as a backup seems like overkill to me, and strictly speaking it violates the 'designated initializer' pattern.  Who are you expecting to use your class that won't use it correctly?

Hmm...I don't quite follow what you're trying to say, especially the part about violating the 'designated initializer' pattern. I suspect there is a bit of miscommunication here. Let me try again. (and please, don't get too wrapped up with my initWithTitle example--it's just a hypothetical example to give us something concrete to talk about).

Let's say I want to create a subclass of UIView called TitleView. And, I want to provide a custom init method called initWithTitle:. In Objective-C I would do the following:

// Create my new designated initializer
- (id)initWithTitle:(NSString*)title {

    // Step 1: Call the super class's designated initializer 
    // and assign the return value to self.
    self = [super initWithFrame:CGRectZero];

    // Step 2: Check to make sure self != nil
    if (self) {

        // Step 3: initialize values
        _title = title;
        [self sizeToFit];
    }

    // Step 4: return self
    return self;

// Step 5: Override Superclass's Designated Initializer
- (id)initWithFrame:(CGRect)frame {

    // Step 6: This must call my new designated initializer
    return [self initWithTitle:nil];
}

This properly sets up the initialization chain for my new TitleView class. I can now create new TitleView objects using ANY of it's inherited init methods. Whether I call init, initWithFrame: or initWithTitle:, I will get correctly routed to initWithTitle:, and then passed on up the chain of designated initializers.

Note: Some developers skip steps 5 and 6. However, if you do this, you are limiting yourself to always calling your own designated initializer. You cannot call any of the other, inherited initializers. This may be OK within the confines of your own application--but (in my mind) it is not acceptable for shared code. There is an implicit contract in Objective-C objects. We should be able to call any of an object's init methods and get back a properly initialized object.

Unfortunately, as far as I can tell, this is simply not possible in Ruby. Step 1 is the real problem. I cannot reassign self, and I cannot call an arbitrary method on the superclass. Please correct me if I'm wrong, but as far as I know, I can only use super to call the superclasses version of the method I'm currently inside.

I can sort of fake it by skipping steps 5 and 6, and just call the superclass's designated initializer directly--but then (as described in my note), I have an incomplete implementation, and I'm limited myself (and anyone else who uses my class) to just the designated initializer. This still might be the best (only?) option--but I don't really like it.

However, there is still one unresolved problem. How do I handle the case where the superclass' s init method returns an object other than self?

Thanks again,

-Rich-


Dan Hixon

unread,
Apr 10, 2014, 11:46:25 AM4/10/14
to rubym...@googlegroups.com
Hi Rich,

Did you ever find a solution to this issue?

I want to subclass SpriteKitNode... I want a Car, I'd love to assign to self to do something like this:

class Car < SpriteKitNode
   def init()
      self = SKSpriteNode.initWithImageNamed("car")
      # other stuff
      self
   end
end

I guess the ruby motion equivalent is this but it isn't as convenient:

class Car < SpriteKitNode
   def init()
      super.tap do |s|
      s.texture = SKTexture.textureWithImageNamed("car")
      end
   end
end

Colin T.A. Gray

unread,
Apr 10, 2014, 1:18:20 PM4/10/14
to rubym...@googlegroups.com
The only difference is you don't assign it to self.  And if 'super' assigns a different value to self, the value in your Ruby code should be updated; if it's not, let me know!  If you really want to check for nil:

class Car < SKNode

  def init
    if super
      self.texture = SKTexture.textureWithImageNamed("car")
    end
    return self
  end

end


But I think this is overkill; I've never (going back to OS X 10.2) seen an init method return 'nil' (it's rumored to happen in low memory situations).  It's a very old habit of obj-c devs, but I think it's unnecessary.

class Car < SKNode

  def init
    super.tap do
      self.texture = SKTexture.textureWithImageNamed("car")
    end
  end

end

For instance, you *never* see people do this, even though it's basically the same idea as checking for 'self' in init:

NSMutableArray *array = [[NSMutableArray alloc] init];
if ( array ) {
  // nice, we have enough memory! sweet!
} else {
  // deal with out-of-memory situation... haha, yeah, right. I'll get right on
  // that...
}


Colin T.A. Gray
Community Manager
HipByte


--
You received this message because you are subscribed to the Google Groups "RubyMotion - Ruby for iOS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubymotion+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubymotion/4e51a32d-9357-45d1-8a3c-91f4b986bfab%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages