123

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

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

我确实修改了 MVCNetworking 项目,删除了 App Delegate 中对 +applicationStartup 的调用,并将管理位放入 +load... 我的计算机没有着火,但这并不意味着它是正确的!我希望了解您必须调用的自定义设置方法与 +load 或 +initialize 的任何微妙之处、陷阱和诸如此类的东西。


对于 +load 文档说:

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

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

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

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


至于 +initialize,文档说:

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

我认为这意味着,“如果你试图设置类......不要使用初始化。” 好的。什么时候或为什么我会覆盖初始化呢?

4

2 回答 2

193

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类对象之后,将消息发送到类对象。但是您不知道您自己的可执行文件中还有哪些其他类已收到。loadloadload

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

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

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

initialize方法_

运行时initialize在将第一条消息(除了loador 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

运行时在函数中发送initialize消息。可以看到它是用来发送的,也就是普通的消息发送功能。 _class_initializeobjc-initialize.mmobjc_msgSend

进一步阅读

查看Mike Ash 周五关于此主题的问答。

于 2012-11-10T22:23:05.567 回答
17

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

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

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

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

这就是为什么你永远不应该+initialize在一个类别中覆盖 - 事实上,尝试替换一个类别中的任何方法是非常危险的,因为你永远不确定你要替换什么,或者你自己的替换是否会被另一个类别切换出来。

顺便说一句,另一个需要考虑的问题+initialize是,如果有人将您子类化,您可能会为您的类调用一次,并且为每个子类调用一次。如果您正在执行诸如设置static变量之类的操作,您将需要防范这种情况:无论是使用dispatch_once()还是通过测试self == [MyClass class]

于 2012-11-10T22:28:50.290 回答