NSObject +加载和+初始化 - 它们做什么?

时间:2012-11-10 21:53:51

标签: objective-c nsobject

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

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

我确实修改了MVCNetworking项目,将App Delegate中的调用移除到+ applicationStartup,并将内务位置放入+ load ...我的计算机没有着火,但这并不意味着它是正确的!我希望能够理解你需要调用的自定义设置方法的任何细微之处,陷阱和其他内容,而不是+ load或+ initialize。


对于+ load文档说:

  

将加载消息发送到两者的类和类别   动态加载和静态链接,但仅限于新加载的   class或category实现了一个可以响应的方法。

如果你不知道所有单词的确切含义,这句话​​就很难解析了。救命啊!

  • “动态加载和静态链接”的含义是什么?某些东西可以动态加载和静态链接,还是互相排斥?

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


至于+ initialize,文档说:

  

初始化它每个类只调用一次。如果你想表演   类的独立初始化和类的   class,你应该实现加载方法。

我认为这意味着,“如果您尝试设置课程......请不要使用初始化。”好的。何时或为什么我会覆盖初始化呢?

2 个答案:

答案 0 :(得分:179)

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的方法,

}

}

运行时将Subclass消息发送到类对象后,它将Superclass消息发送到所有类的超类对象(如果这些超类对象实现load)并且全部您链接到的共享库中的类对象。但是您不知道自己的可执行文件中的哪些类已经收到load

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

您可以在objc-runtime-new.mmload中查看运行时如何查找load方法作为特例,并直接从loadobjc-loadmethod.mm调用它}}

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

load方法

运行时在将第一个消息(loadinitialize除外)发送到类对象或类的任何实例之前调用类对象上的initialize方法。此消息是使用常规机制发送的,因此如果您的类没有实现load,但是从类继承,则您的类将使用其超类的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 } } 重新初始化。

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

进一步阅读

在此主题上查看Mike Ash's Friday Q&A

答案 1 :(得分:17)

这意味着不要覆盖某个类别中的+initialize,您可能会破坏某些内容。

+load每个类或类别调用一次,只要加载该类或类别,就会实现+load。当它说“静态链接”时,它意味着编译成你的应用程序二进制文件。这样编译的类的+load方法将在您的应用程序启动时执行,可能在它进入main()之前执行。当它表示“动态加载”时,它表示通过插件包或对dlopen()的调用加载。如果你在iOS上,你可以忽略这种情况。

在消息处理该消息之前,第一次将消息发送到类时调用

+initialize。这(显然)只发生过一次。如果您覆盖某个类别中的+initialize,则会发生以下三种情况之一:

  • 调用您的类别实现,而类的实现不会
  • 其他人的类别实现被调用;没有你写的
  • 您的类别尚未加载,其实现永远不会被调用。

这就是为什么你永远不应该覆盖某个类别中的+initialize - 实际上尝试替换某个类别中的任何方法是非常危险的,因为你永远无法确定你是什么更换或者您自己的替换件本身是否会被其他类别更换。

BTW,+initialize需要考虑的另一个问题是,如果某人将您作为子类,您可能会为您的类调用一次,为每个子类调用一次。如果您正在设置static个变量,那么您需要防范:dispatch_once()或测试self == [MyClass class]