Reputation: 4657
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 self
changes the actual type in a way that does not happen when assigning to a regular variable?
Upvotes: 0
Views: 109
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
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
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