-init与+initialize的实现方式

5
有人能解释一下为什么我们需要在+initialize方法中包含if (self == SomeClass class)吗?
我发现了类似的问题(如下所列),但没有找到具体的解释:
  1. Objective-C: init vs initialize
  2. Should +initialize/+load always start with an: if (self == [MyClass class]) guard?
每个人都说,如果你不在子类中实现/覆盖+initialize,那么它会调用父类两次。
有人能特别解释一下这部分内容,具体来说是为什么会调用父类两次吗?
最后,为什么当我们在继承自NSObject的类中实现+initialize时,创建一个自定义的-init方法并调用self = [super init];时,不会发生这种情况?

init和initialize彼此无关。 - rmaddy
@rmaddy 在 -init 方法中,你有 self = [super init]; 这一行代码,它会调用父类的 +initialize 方法,我说的对吗? - makaed
1
你是不正确的。initialize在任何其他类或实例方法被调用之前,每个类只会被调用一次。 - rmaddy
@rmaddy这就是为什么我感到疑惑并请求澄清。它何时被调用?如果我不包括if语句,它怎么会被调用两次。 - makaed
3个回答

5

假设您有一个实现 +initialize 的超类和一个不实现该方法的子类。

@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

使用超类。这将引发对+[SuperClass initialize]的调用。

[SuperClass class];
=> This is class SuperClass running SuperClass +initialize

现在使用子类。运行时在SubClass中寻找+initialize的实现,但没有找到任何东西。然后在SuperClass中寻找继承的实现并找到它。即使已经代表SuperClass本身调用过一次,也会调用继承的实现:
[SubClass class];
=> This is class SubClass running SuperClass +initialize

守卫允许你执行最多只运行一次的工作。任何后续对 +initialize 的调用都会有不同的类作为 self,因此守卫可以忽略它们。

4

-init+initialize是完全不同的东西。第一个是用于初始化实例; 第二个是用于初始化

每次消息发送到某个给定的类时,运行时都会确保在它及其超类上调用+initialize。由于任何子类在初始化自身之前需要准备好其超类,因此先初始化超类。

因此,第一次发送到YourSubclass的时间,运行时可能会执行以下操作:

[NSObject initialize];
[YourClass initialize];
[YourSubclass initialize];

(虽然这很不可能是第一次发送消息给NSObject,所以可能在这一点上它不需要被初始化。这只是为了说明问题。)

如果YourSubclass没有实现+initialize,那么上面显示的[YourSubclass initialize]调用实际上会调用+[YourClass initialize]。这就是正常的继承机制起作用。这将导致+[YourClass initialize]第二次被调用。

由于+initialize方法中所做的工作通常是只应该做一次的事情,因此需要使用保护条件if (self == [TheClassWhoseImplementationThisMethodIsPartOf class])。此外,这项工作通常假定self指的是当前编写的类,因此也是保护条件的原因。

你引用的第二个答案指出了一个例外情况,即使用旧的机制通过+setKeys:triggerChangeNotificationsForDependentKey:方法注册KVO依赖键。该方法仅适用于实际调用它的类,而不适用于任何子类。你应该避免使用它,并使用更现代的+keyPathsForValuesAffectingValueForKey:+keyPathsForValuesAffecting<Key>方法。如果必须使用旧方式,请将该部分放在保护条件外面。此外,这样的类的子类必须调用super,这通常不会做。

更新:

+initialize方法通常不应调用super,因为运行时已经初始化了超类。只有当超类已知使用旧机制注册依赖键时,任何子类都必须调用super

与此相反,在-init的情况下不存在同样的担忧,因为运行时不会在调用你的方法之前自动调用超类的init方法。实际上,如果你的init方法没有调用super,那么没有东西会初始化实例的超类“部分”。


在你的例子中,TheClassWhoseImplementationThisMethodIsPartOf 是否是 YourClass - makaed
1
+[YourClass initialize] 的实现中,是的。我尝试让它更通用。 - Ken Thomases

0

你提到的问题都有很好的被接受的答案。总结一下,+initialize会在每个类上被运行时调用,因此对于一个具有N个子类的超类,它将在超类上被调用N+1次(直接调用一次,每个继承它的子类再调用一次)。如果子类重写它并调用super,情况也是一样的。

您可以通过在超类级别询问“这是系统直接初始化的,而不是由我的子类继承或调用的‘super’吗?”来防止这种情况发生。

if (self == [ThisSuperclass self]) {}

-init 用于初始化类的实例,不像+initialize那样默认情况下由运行时调用。实例继承其 -init 的实现,可以重写继承的实现,也可以通过调用 [super init]; 来享受继承的实现带来的好处。


那么 +initialize 无论如何都会被运行时调用,而不需要用户编写代码吗?如果是这样,我想知道在子类中什么情况下会调用超类的 +initialize 方法。 - makaed
1
@danh,当子类调用super时,方法被调用两次并不是唯一的情况。当子类没有实现+initialize时,这种情况也很常见。 - Ken Thomases

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接