在OC中, 根类NSObject或其子类被加载和初始化的时候,会触发一些方法,可以在适当的情况下做一些定制处理。其实, 这正是对应着load
和initialize
方法。
+ (void)load;
+ (void)initialize;
+load方法的执行时机在 App启动后, 而且是在main函数
之前。
当main函数
执行前,正是从dyld
(the dynamic link editor)到runtime
的过程。大致过程如下:
dyld
读取并加载程序的mach-o文件,即程序的二进制可执行文件。framework
、tbd
等。ImageLoader
加载所有image
(镜像)到内存。map_images
函数执行。load_images
函数执行,进而触发+load方法。C++
全局静态变量和__attribute__
修饰的构造函数的初始化。main函数
执行。一个类的+load方法不需要显式调用[super load]
,且父类会先执行自己类中的+load方法,再执行子类的+load方法。
类的category中的+load方法也会执行,并且是先执行类后执行分类的顺序。
每个类中的+load方法只会执行一次。当子类执行自己类中的+load方法前,它的父类会先执行自己的+load方法,且仅会执行一次。+load方法不需要显式调用[super load]
,但也不会多次执行父类中+load方法的实现。即使在子类中手动调用[super load]
,+load方法也只会执行一次。
对于某个类, 当category中重写某个方法时, 只会执行category中的此方法, 并不会执行原类中的此方法。即方法覆盖了。
+load方法在category中的使用有另外一个特性。在category中重写+load方法后并不会影响其原来类的+load方法执行, 而是如上面所说先类后分类的顺序执行。
有个重要的事情需注意,那就是+load方法并不像普通的方法那样,它并不遵从那套继承规则。如果某个类本身没实现load方法,那么不管其各级父类是否实现此方法,系统都不会调用。所以,这就是为什么+load方法只会执行一次的原因。
+load方法的执行时机在main函数
之前,运行环境还是相对比较危险的。但并不能保证所有类都加载完成且可用。
但因为此时C++静态初始化程序还没有运行, 但你链接的framework在此可以确保完全加载了。
在+load中必要时还要自己负责做 autorelease处理。
+load方法中的执行在App的启动过程中,任何操作都会影响App启动时长,所以尽量少的在+load方法中添加代码。
你可以看到在运行时如何查找的+load方法作为一种特殊情况_class_getLoadMethod
的objc-runtime-new.mm,并直接调用它call_class_loads
的objc-loadmethod.mm。
当类收到第一条消息之前初始化该类,初始化每个类只调用一次。与+load的加载时机不相同, 类似于懒加载的方式,所以也可能根本不会被调用。
一个类的+initialize方法不需要显式调用[super load]
,且父类会先执行自己类或者分类中的+initialize方法,再执行子类的+initialize方法。
类的category中的+initialize方法会覆盖原类中的+initialize方法。
与+load方法不一样,+initialize方法可能会多次调用。+initialize方法同样不需要显式调用[super load]
,但区别在于[super load]
会执行父类中+load方法的实现。所以当父类和子类都重写+initialize方法后,可能造成父类中+initialize方法的多次调用。
官方文档中也明确说明,如果您想保护自己的类不被多次执行,您可以按照以下方式构建您的实现:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
+initialize方法就没有+load那么特殊了,它与普通方法是一样的。当category中重写某个方法时, 只会执行category中的此方法, 并不会执行原类中的此方法了。所以就会方法覆盖了,只会执行最后一个分类中的+initialize方法的实现。
运行时以线程安全的方式将初始化消息发送给类。也就是说,初始化由第一个线程运行以将消息发送到类,并且尝试向该类发送消息的任何其他线程将阻塞,直到初始化完成。所以+initialize的运行过程中是能保证线程安全的。
在+initialize方法收到调用时,运行环境已经基本健全了。所以, +initialize的环境不会有较大隐患。
initialize
只应该用来设置内部数据。不应该在其中调用其他方法, 即便是本类自己的方法, 也最好别调用。若某个全局状态无法在编译期初始化, 则可以放在 initialize
里来做。
运行时initialize
在_class_initialize
函数中发送消息objc-initialize.mm。您可以看到它用于objc_msgSend
发送它,这是普通的消息发送功能。
最后,在load
和initialize
方法中尽量精简代码,在里面设置一些状态,使本类能够正常运作就可以了,不要执行那种耗时太久或需要加锁的任务。
而且在任何时候,都不要过重地依赖于这两个方法,尤其是+load方法。
Objective-C Class Loading and Initialization
NSObject +load and +initialize - What do they do?