Search code examples
objective-cinitializationmacrubyrubymotion

Calling a different superclass init method from custom initializer in MacRuby


In Objective C, a custom init method must call the superclass's designated initializer. Consider the following custom init method for a sublcass of NSView:

- (void)initWithFrame:(CGRect)aFrame andName:(NSString *)aName {
    if(self = [super initWithFrame:aFrame]){
        //set up object
    }
}

However, MacRuby only offers the super keyword, which simply tries calling the same method in the superclass. Since this is a custom initializer, however, the superclass has no such method.

What is the standard way of solving this? I did see this question: MacRuby custom initializers but I can't say I understand the answer very well, nor does it seem to be some broadly accepted solution. Since MacRuby is now a few years older than when that post was written, I'm hoping a clearer, standard solution now exists.


Solution

  • In rubymotion (and I think MacRuby shares this behavior) there are TWO types of classes: pure Ruby, which use initialize, and classes derived from an objective-c class. It is best not to mix the two. If you are subclassing a objective-c class, you should use an init method. your initialize method will never get called!

    This is because the NSObject##new method calls init, not initialize (actually even this is an oversimplification, but the abstraction works).

    your example above is confusing because you are calling init instead of initWithFrame. You MUST call the designated initializer of the parent class - in this case, using super.

    If the initializer you call (in this case, init, calls your initializer, you're using the wrong one - there's no escaping the recursion cycle in that case.

    So you can't exactly CHOOSE which method to call on the parent. You can, however, use your own init method:

    def initWithTitle(title)
      initWithFrame(CGRectZero)
      @title = title
      return self
    end
    

    I would write this method like this:

    def initWithTitle(title)
      initWithFrame(CGRectZero).tap do
        @title = title
      end  # that way I never forget to return self
    end