Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't a designated initializer call a secondary initializer in its base class?

According to the documentation, a class's designated initializer in Objective-C must call the designated initializer of its base class.

Another rule is that secondary initializers must call the designated initializer of their own class.

But if the second rule is followed, why can't a designated initializer call a secondary initializer in its base class? This base secondary initializer will ultimately call its own level's D.I., so the object will still be properly initialized, right?

The difference seems to be who chooses defaults for missing variables -- you or your base class.

like image 729
Halt Avatar asked Feb 03 '23 06:02

Halt


2 Answers

Let's consider NSSet. It has a designated initializer:

- (id)initWithObjects:(const id *)objects count:(NSUInteger)cnt {
   // initialization code here
   return self;
}

It also has some secondary initializers, like this one:

- (id)initWithArray:(NSArray *)array {
    NSUInteger count = array.count;
    id objects[count];
    [array getObjects:objects range:NSMakeRange(0, count)];
    return [self initWithObjects:objects count:count];
}

Now you want a subclass of NSSet that automatically rejects the string "Bob". So you dutifully override the designated initializer in your subclass, but you call one of super's secondary initializers:

@implementation BobRejectingSet

- (id)initWithObjects:(const id *)objects count:(NSUInteger)count {
    NSMutableArray *array = [[NSMutableArray alloc] initWithCount:count];
    for (NSUInteger i = 0; i < count; ++i) {
        if (![objects[i] isEqual:@"Bob"]) {
            [array addObject:objects[i]];
        }
    }
    return [super initWithArray:array];
}

What happens when you do this:

BobRejectingSet *noBobs = [[BobRejectingSet alloc] initWithArray:someObjects];

Since you didn't override initWithArray:, the program calls -[NSSet initWithArray:], which calls the designated initializer, initWithObjects:count:. You overrode the designated initializer, so it calls your method. Your method filters out the Bobs, and then calls super's secondary initializer, initWithArray:… which turns around and calls your designated initializer override again. Unlimited recursion. Stack overflow. You get the segmentation-violation-core-dumped blues.

That's why you always use super's designated initializer.

like image 68
rob mayoff Avatar answered Feb 05 '23 17:02

rob mayoff


If a designated initializer were to call a secondary initializer in its own class, then it wouldn't be serving the role of designated initializer. The point of the designated initializer is so a subclass implementer knows the single initializer s/he can override that all other initializers will funnel through.

If a designated initializer in a subclass were to call a non-designated initializer in its superclass, then the superclass initializer would likely turn around and call another of the initializers. Due to the dynamic nature of method dispatch, it can't restrict that to calling only the methods implemented at its level. That call is very likely to be to one of the subclass's overrides, which would funnel into the subclass's designated initializer, resulting in infinite recursion.

like image 41
Ken Thomases Avatar answered Feb 05 '23 17:02

Ken Thomases