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-