pajevic
pajevic

Reputation: 4657

Why is it ok instantiatie self from a super init method?

I was doing some coding where I had a class MyClass which inherits from class MySuperClass. MyClass has a property myProperty.

So I was creating an instance of this class from JSON and in a moment of thoughtlessness I wrote my method like this:

+ (instancetype)newFromJSON:(NSDictionary *)json {
    MyClass *myObject = [super newFromJSON:json];
    myObject.myProperty = someValue;
    return myObject;
}

Note that MySuperClass does have a method + (instancetype)newFromJSON:(NSDictionary *)json.

Now, this obviously doesn't work since the call to super newFromJSON will return an instance of MySuperClass which would become the actual type of myObject. This will of course give me a runtime error since MySuperClass doesn't have a myProperty property.

But this got me thinking about something. Why are we able to do seemingly the same thing when we are instantiating objects with a call to [super init]?

Why is it ok to do this:

- (instancetype)init {
    self = [super init];
    if (self) {
        self.myProperty = someValue;
    }
    return self;
}

Is it because init methods are treated specially in this regard like they are in so many other? Or is it perhaps that assigning to selfchanges the actual type in a way that does not happen when assigning to a regular variable?

Upvotes: 0

Views: 109

Answers (3)

Mobile Ben
Mobile Ben

Reputation: 7331

There is a bit of a difference in when you are doing + (instancetype)newFromJSON:(NSDictionary *)json versus init. The former is doing both an allocation of memory and initialization of the new instance. init is solely doing the initialization of the instance.

init is special during compilation, in that it does expect you to call [super init] (it will warn you). But effectively it is saying "use my superclass to initialize me first".

Note to do what you want is possible. You just need to have the superclass modify how it allocates memory. You need to do something like:

Parent *myObject = [[[super class] alloc] init];

Here is a code example to hopefully illustrate these points.

Let's say you have these classes:

@interface Parent : NSObject

@property (nonatomic, assign) NSInteger someValue;

+ (instancetype)newInstance;

- (instancetype)init;

@end

@implementation Parent

+ (instancetype)newInstance {
    Parent *myObject = [[[super class] alloc] init];

    NSLog(@"Creating new item of class %@", NSStringFromClass([myObject class]));
    return myObject;
}

- (instancetype)init {
    // This [super init] calls NSObject's init
    self = [super init];

    if (self) {
        _someValue = 1000;
    }

    return self;
}

@end

@interface ClassA : Parent

@property (nonatomic, assign) NSInteger otherValue;

@end

@implementation ClassA

+ (instancetype)newInstance {
    ClassA *myObject = [super newInstance];

    myObject.otherValue = 2000;

    return myObject;
}

- (instancetype)init {
    // This [super init] calls ClassA's init
    self = [super init];

    if (self) {
    }

    return self;
}

@end

@interface ClassB : Parent

@end

@implementation ClassB

// Default init will be Parent's

@end


@interface ClassC : Parent

@end

@implementation ClassC

- (instancetype)init {
    // We are not calling [super init];
    // NOTE: This will yield a warning since we are not calling super

    return self;
}

@end

If you execute:

    ClassA *classA = [[ClassA alloc] init];
    ClassB *classB = [[ClassB alloc] init];
    ClassC *classC = [[ClassC alloc] init];
    Parent *newInstanceParent = [Parent newInstance];
    ClassA *newInstanceClassA = [ClassA newInstance];

    NSLog(@"classA.someValue = %ld, classB.someValue = %ld, classC.someValue = %ld", classA.someValue, classB.someValue, classC.someValue);
    NSLog(@"classA.otherValue = %ld, newInstanceClassA.otherValue = %ld", classA.otherValue, newInstanceClassA.otherValue);
    NSLog(@"newInstanceParent is %@, newInstanceClassA is %@", NSStringFromClass([newInstanceParent class]), NSStringFromClass([newInstanceClassA class]));

You'll get output of:

Creating new item of class Parent

Creating new item of class ClassA

classA.someValue = 1000, classB.someValue = 1000, classC.someValue = 0

classA.otherValue = 0, newInstanceClassA.otherValue = 2000

newInstanceParent is Parent, newInstanceClassA is ClassA

Upvotes: 0

bbum
bbum

Reputation: 162712

A few points of clarification:

+ (instancetype)newFromJSON:(NSDictionary *)json {
    MyClass *myObject = [super newFromJSON:json];
    myObject.myProperty = someValue;
    return myObject;
}

When you invoke [super newFromJSON:json], all you are doing is telling the Objective-C runtime to start the search for the method newFromJSON: from self's superclass.

It is not changing the class self.

So, yes, that code is correct and will work fine.

Furthermore, there is absolutely nothing special about the init method and its treatment of super.

Upvotes: 0

Avi
Avi

Reputation: 7552

The super keyword only indicates from where in the inheritance chain to start looking to find the selector (method) you are invoking. It says to start looking at the current instance's superclass, instead of the instance's class.

What it does not do is change the class type of the self parameter implicitly passed to a method.

Thus, when invoking [super init], the init implementation in the superclass still receives a reference to MySubClass (or whatever).

Note: you can find documentation which states that init may return a different class than the one on which it was invoked. This is common for class clusters. This is because the idiomatic implementation of init simply returns self without constructing a new instance, but it's allowed to.

Upvotes: 2

Related Questions