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

我感兴趣的是了解导致开发人员覆盖 + 初始化或 + 加载的情况。文档清楚地表明这些方法是 Objective-C 运行时为您调用的,但是从这些方法的文档中可以清楚地看到这一点。:-)

我的好奇心来自于苹果的示例代码—— MVCNetworking。他们的模型类有一个 +(void) applicationStartup方法。它在文件系统上做一些管理工作,读取 NSDefault 等等。.并且,在尝试了解 NSObject 的类方法之后,似乎可以将这个清洁工作放入 + load 中。

我确实修改了 MVCNetworking 项目,删除了对 + applicationStartup 应用程序委托的调用,并将管理部分放入 + load... 我的电脑没有着火,但这并不意味着它是正确的!我希望能够了解一些关于自定义设置方法的细微之处、常见问题和诸如此类的东西,这些方法是您必须调用的,而不是 + load 或 + initialize。


For + load 文档显示:

加载消息被发送到既是 动态加载和静态链接,但只有在新加载的 类或类别实现一个可以响应。

如果你不知道所有单词的确切意思,那么这个句子就很复杂,很难解析!

  • “动态加载和静态链接”是什么意思某些东西可以动态加载和静态链接,还是它们相互排斥?

  • "...the newly loaded class or category implements a method that can respond" What method? Respond how?


至于 + initialize,文档说:

初始化每个类只调用一次 类和类别的独立初始化 类,则应该实现加载方法。

I take this to mean, "if your trying to setup the class... don't use initialize." Okay, fine. When or why would I override initialize then?

44100 次浏览

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类对象。即使 SubclassSuperclass继承了该方法,它也会将 load消息发送给 Subclass类对象。

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

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

您可以看到运行时如何在 objc-runtime-new.mm_class_getLoadMethod中将 load方法作为特例查找,并从 objc-loadmethod.mm中的 call_class_loads直接调用它。

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

initialize方法

运行库在向类对象或类的任何实例发送第一条消息(不包括 loadinitialize)之前调用类对象上的 initialize方法。这条消息是使用普通机制发送的,因此如果您的类没有实现 initialize,而是从实现了 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时重新初始化自身。

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

进一步阅读

请查看关于这个主题的 Mike Ash 的周五问答

它的意思是,不要在一个类别中覆盖 +initialize,您可能会破坏一些东西。

每个类或类别调用 +load一次,这些类或类别实现装载的 +load尽快。当它说“静态链接”时,意思是编译成你的应用程序二进制文件。这样编译的类上的 +load方法将在应用程序启动时执行,可能在它进入 main()之前。当它说“动态加载”时,它的意思是通过插件包加载或者调用 dlopen()。如果你在 iOS 上,你可以忽略这个案子。

第一次将消息发送到类时,就在它处理该消息之前调用 +initialize。这(显然)只发生一次。如果在一个类别中覆盖 +initialize,会发生以下三种情况之一:

  • 您的类别实现被调用,而类的实现没有被调用
  • 调用其他人的类别实现; 您编写的任何东西都不会调用
  • 您的类别尚未加载,其实现从未被调用。

这就是为什么你永远不应该在一个类别中覆盖 +initialize——事实上,尝试在一个类别中替换 任何方法是相当危险的,因为你永远不知道你正在替换什么,或者你自己的替换是否会被另一个类别替换掉。

顺便说一句,使用 +initialize需要考虑的另一个问题是,如果有人对您的子类进行了调用,那么您可能会对您的类进行一次调用,对每个子类进行一次调用。如果您正在做类似于设置 static变量的事情,那么您需要防止这种情况发生: 使用 dispatch_once()或测试 self == [MyClass class]