Can anyone explain why we need to include if (self == SomeClass class)
inside the +initialize
method?
I’ve found similar questions (listed below), but didn’t find any specific clarifications:
- Objective-C: init vs initialize
- Should +initialize/+load always start with an: if (self == [MyClass class]) guard?
Everyone says that if you don’t implement/override +initialize
in Subclass, then it’s going to call the parent class twice.
Can anyone explain that part in particular, specifically why does it call the parent class twice?
Lastly, why doesn’t it happen when we implement +initialize
in the class that inherits from NSObject, where we create a custom -init
method and call self = [super init];
.
-init
and +initialize
are completely different things. The first is for initializing instances; the second is for initializing classes.
The first time any given class is messaged, the runtime makes sure to invoke +initialize
on it and its superclasses. The superclasses are initialized first because they need to be ready before any subclass can initialize itself.
So, the first that time YourSubclass
is messaged, the runtime might do something like:
[NSObject initialize];
[YourClass initialize];
[YourSubclass initialize];
(Although it's very unlikely that this would be the first time that NSObject
is messaged, so probably it doesn't need to be initialized at this point. It's just for illustration.)
If YourSubclass
doesn't implement +initialize
, then the [YourSubclass initialize]
invocation shown above will actually call +[YourClass initialize]
. That's just the normal inheritance mechanism at work. That will make the second time that +[YourClass initialize]
has been called.
Since the work done in a +initialize
method is usually the sort of thing that should only be done once, the guard if (self == [TheClassWhoseImplementationThisMethodIsPartOf class])
is necessary. Also, that work often assumes that self
refers to the current class being written, so that's also a reason for the guard.
The second answer you cite notes an exception, which is the old-style mechanism for registering KVO dependent keys with the +setKeys:triggerChangeNotificationsForDependentKey:
method. That method is specific to the actual class it's invoked on, not any subclasses. You should avoid it and use the more modern +keyPathsForValuesAffectingValueForKey:
or +keyPathsForValuesAffecting<Key>
methods. If you must use the old way, put that part outside of the guard. Also, subclasses of such a class must call through to super
which is not normally done.
Update:
A +initialize
method should not normally call through to super
because the runtime has already initialized the superclass. If and only if the superclass is known to register dependent keys using the old mechanism, then any subclasses must call through to super.
The same worry does not exist in the case of -init
because the runtime is not automatically calling the superclass init method for you before calling yours. Indeed, if your init method does not call through to super
, then nothing will have initialized the superclass's "part" of the instance.
Imagine you have a superclass that implements +initialize
and a subclass that does not.
@interface SuperClass : NSObject @end
@implementation SuperClass
+(void)initialize {
NSLog(@"This is class %@ running SuperClass +initialize", self);
}
@end
@interface SubClass : SuperClass @end
@implementation SubClass
// no +initialize implementation
@end
Use the superclass. This provokes a call to +[SuperClass initialize]
.
[SuperClass class];
=> This is class SuperClass running SuperClass +initialize
Now use the subclass. The runtime looks for an implementation of +initialize
in SubClass
and does not find anything. Then it looks for an inherited implementation in SuperClass
and finds it. The inherited implementation gets called even though it was already called once on behalf of SuperClass
itself:
[SubClass class];
=> This is class SubClass running SuperClass +initialize
The guard allows you to perform work that must be run at most once. Any subsequent calls to +initialize
have a different class as self
, so the guard can ignore them.
The questions you cite have good accepted answers. To summarize, +initialize
is called by the runtime on every class, so for a superclass with N subclasses, it will get called N+1 times on the superclass (once directly and once for each subclass that inherits it). Same thing if a subclass overrides it and calls super.
You can defend against this by asking at the superclass level, "is this my direct initialization by the system, and not 'super' being inherited or called by my subclass?"
if (self == [ThisSuperclass self]) {}
-init
is used to initialize instances of classes and isn't invoked by the runtime by default like +initialize
. Instances inherit their implementations of -init, can override the inherited implementation, and can also enjoy the benefit of the inherited implementation by calling [super init];
.
来源:https://stackoverflow.com/questions/21200377/implementation-of-init-vs-initialize