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

ede*_*y05 112 objective-c nsobject

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

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

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


对于+ load文档说:

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

如果你不知道所有单词的确切含义,那么这句话很难解析.救命!

  • 什么是"动态加载和静态链接?" 某些东西可以动态加载和静态链接,还是它们是互斥的?

  • "......新加载的类或类实现了一个可以响应的方法"什么方法?回应怎么样?


至于+ initialize,文档说:

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

我的意思是,"如果你试图设置类......不要使用初始化." 好的.何时或为什么我会覆盖初始化呢?

rob*_*off 183

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
Run Code Online (Sandbox Code Playgroud)

运行时将load消息发送到Superclass类对象.它并没有发送load消息给Subclass类对象,即使Subclass继承的方法Superclass.

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

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

你可以看到在运行时如何查找的load方法作为一种特殊情况_class_getLoadMethodobjc-runtime-new.mm,并直接调用它call_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;
}
Run Code Online (Sandbox Code Playgroud)

该程序打印两行输出:

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
Run Code Online (Sandbox Code Playgroud)

由于系统initialize懒惰地发送方法,因此除非程序实际将消息发送到类(或子类,或类或子类的实例),否则类不会接收消息.到你收到的时候initialize,你的过程中的每个班级都应该已经收到load(如果适用的话).

规范的实施方式initialize是:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}
Run Code Online (Sandbox Code Playgroud)

这种模式的重点是避免Someclass在它有一个没有实现的子类时重新初始化initialize.

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

进一步阅读

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

  • 您应该注意,"+ load"是针对类别单独发送的; 也就是说,类中的每个类都可以包含自己的`+ load`方法. (25认同)
  • 你首先收到`load`.然后,当"load"仍在运行时,您可能会收到`initialize`. (5认同)

小智 17

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

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

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

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

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

顺便说一句,另一个需要考虑的问题+initialize是,如果某人为您做了子类,您可能会为您的类调用一次,为每个子类调用一次.如果您正在做类似设置static变量的操作,那么您需要防范:使用dispatch_once()或通过测试self == [MyClass class].