NSObject + load和+ initialize-它们做什么?


115

我有兴趣了解导致开发人员覆盖+ initialize或+ load的情况。文档清楚地表明,Objective-C运行时会为您调用这些方法,但是从这些方法的文档中确实可以看出所有这些。:-)

我的好奇心来自查看苹果的示例代码-MVCNetworking。他们的模型类有一个+(void) applicationStartup方法。它在文件系统上做一些内务处理,读取NSDefaults等等,等等,并且在尝试使用NSObject的类方法后,似乎可以将这项工作放到+ load中。

我确实修改了MVCNetworking项目,删除了App Delegate中对+ applicationStartup的调用,并将整理位放入+ load ...我的计算机没有着火,但这并不意味着它是正确的!我希望对您必须调用+ load或+ initialize的自定义安装方法周围的任何细微之处,陷阱和注意事项有所了解。


对于+ load文档说:

加载消息将发送到动态加载和静态链接的类和类别,但前提是新加载的类或类别实现了可以响应的方法。

如果您不知道所有单词的确切含义,那么这句话很笨拙且难以解析。救命!

  • “动态加载和静态链接两者”是什么意思?是否可以动态加载和静态链接某些东西,或者它们是互斥的?

  • “ ...新加载的类或类别实现了可以响应的方法”什么方法?反应如何?


至于+ initialize,文档说:

初始化它,每个类仅调用一次。如果要对类和类的类别执行独立的初始化,则应实现load方法。

我的意思是,“如果您要设置类...请不要使用初始化”。好的。什么时候或为什么要覆盖初始化?

Answers:


184

load消息

load在将类对象加载到进程的地址空间后不久,运行时将消息发送到每个类对象。对于属于程序可执行文件一部分的类,运行时会load在进程的生命周期的很早就发送消息。对于共享(动态加载)库中的类,运行时会在将共享库加载到进程的地址空间后立即发送加载消息。

此外,运行时仅load在该类对象本身实现该load方法的情况下才发送给该类对象。例:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)load {
    NSLog(@"in Superclass load");
}

@end

@implementation Subclass

// ... load not implemented in this class

@end

运行时将load消息发送到Superclass类对象。即使从继承方法,它也不会将load消息发送到Subclass类对象。SubclassSuperclass

在将load消息发送load到所有类的超类对象(如果实现了这些超类对象load)以及您链接到的共享库中的所有类对象之后,运行时将消息发送到类对象。但是您不知道自己的可执行文件中还接收load了哪些其他类。

如果进程load实现了该load方法,则您的进程加载到其地址空间中的每个类都会收到一条消息,无论您的进程是否对该类进行了其他任何使用。

你可以看到在运行时如何查找的load方法作为一种特殊情况_class_getLoadMethodobjc-runtime-new.mm,并直接调用它call_class_loadsobjc-loadmethod.mm

运行时还会运行load它加载的每个类别的方法,即使同一类上实现了多个类别load。这很不寻常。通常,如果两个类别在同一类上定义了相同的方法,则其中一个方法将“获胜”并被使用,而另一个方法将永远不会被调用。

initialize方法

initialize在将第一条消息(loadinitialize除外)发送到类对象或任何类实例之前,运行时会在类对象上调用方法。该消息是使用常规机制发送的,因此,如果您的类未实现initialize,但从继承该类的类继承,则您的类将使用其超类的initialize。运行时将首先发送initialize到所有类的超类(如果尚未发送initialize)。

例:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)initialize {
    NSLog(@"in Superclass initialize; self = %@", self);
}

@end

@implementation Subclass

// ... initialize not implemented in this class

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Subclass *object = [[Subclass alloc] init];
    }
    return 0;
}

该程序输出两行输出:

2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass

由于系统initialize延迟地发送方法,因此除非您的程序实际将消息发送到该类(或子类,或该类的实例或子类的实例),否则一个类将不会收到消息。到您收到时initialize,过程中的每个课程都应该已经收到load(如果适用)。

规范的实现方式initialize是:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

此模式的要点是避免Someclass在子类未实现时重新初始化自身initialize

运行时initialize在中的_class_initialize函数中发送消息objc-initialize.mm。您可以看到它objc_msgSend用来发送消息,这是正常的消息发送功能。

进一步阅读

查阅Mike Ash的星期五有关该主题的问答


25
您应该注意,+load是针对类别单独发送的;也就是说,类中的每个类别都可以包含其自己的+load方法。
乔纳森·格林斯潘

1
还要注意,由于引用了未初始化的实体,因此将在必要时initializeload方法正确调用load。这可以(奇怪,但明智地)导致initialize之前运行load!无论如何,这就是我所观察到的。这似乎与“并且在您收到时initialize,过程中的每个课程都应该已经收到load(如果适用)” 相反。
约翰·

5
load首先收到。然后,您可能会收到initializeload仍在运行。
rob mayoff 2014年

1
@robmayoff我们是否不需要在各自的方法内添加[super initialize]和[super load]行?
damithH

1
这通常是个坏主意,因为运行时在将它们发送给您之前,已经将这两个消息都发送给了您的所有超类。
rob mayoff

17

这意味着不要+initialize在类别中覆盖,您可能会破坏某些东西。

+load被称为每班或实现类一次+load只要这个类或类别被加载。当它说“静态链接”时,表示已编译到您的应用程序二进制文件中。+load如此编译的类上的方法将在您的应用启动时(可能在进入之前)执行main()。如果说“动态加载”,则表示是通过插件包或调用加载的dlopen()。如果您使用的是iOS,则可以忽略这种情况。

+initialize在第一次将消息发送到类之前,即在处理该消息之前被称为。这(显然)只发生一次。如果您覆盖+initialize类别,则会发生以下三种情况之一:

  • 您的类别实现被调用而类的实现没有被调用
  • 调用其他人的类别实现;你写的没事
  • 您的类别尚未加载,并且其实现从未被调用。

这就是为什么您永远不能覆盖+initialize类别的原因-实际上,尝试替换类别中的任何方法都是非常危险的,因为您不确定要替换的内容还是自己的替换项本身是否会被另一个类别切换掉。

顺便说一句,要考虑的另一个问题+initialize是,如果有人对您进行了子类化,则您的类可能会被调用一次,而每个子类都会被调用一次。如果您正在执行诸如设置static变量之类的操作,则需要避免这种情况:使用dispatch_once()或通过test进行self == [MyClass class]

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.