+initialize
方法的调用时机
一个类或者它的子类收到第一条消息(手写代码调用,+load
方法不算)之前调用,可以做一些初始化的工作。但该类的+initialize
的方法调用,在其父类之后。
Runtime运行时以线程安全的方式将+initialize
消息发送给类。也就是说,当一个类首次要执行手动调用的代码之前,会等待+initialize
方法执行完毕后,再调用该方法。
这里需要注意的一点:
当子类没有实现+initialize
或者子类在+initialize
中显式的调用了[super initialize]
,那么父类的+initialize
方法会被调用多次。如果希望避免某一个类中的+initialize
方法被调用过多次,可以使用下面的方法来实现:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
因为+initialize
是以阻塞方式调用的,所以很重要的一点就是将方法实现限制为可能最小的工作量。
Demo演示
接下来我们写一个demo来验证一下,首先创建一个Person类,和Person的两个分类Test1,Test2。
@interface Person : NSObject
@end
@implementation Person
+ (void)initialize
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
@interface Person (Test1)
@end
@implementation Person (Test1)
+ (void)initialize
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
@interface Person (Test2)
@end
@implementation Person (Test2)
+ (void)initialize
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
然后新建一个Student类继承Person类,并实现Student的两个分类Test1,Test2。
@interface Student : Person
@end
@implementation Student
+ (void)initialize
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
@interface Student (Test1)
@end
@implementation Student (Test1)
+ (void)initialize
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
@interface Student (Test2)
@end
@implementation Student (Test2)
+ (void)initialize
{
NSLog(@"---- %p %s", self, __FUNCTION__);
}
@end
然后我们什么都不调用,直接运行程序,那应该也不会打印任何信息。
然后,我们调用[Person alloc]
方法,再次运行,查看结果。
---- 0x10f53ef60 +[Person(Test2) initialize]
这里的输出分类的+initialize
方法,查看一下Compile Sources
,发现Person(Test2)
分类在最后编译,所以调用的是它实现的+initialize
方法。
这里,我们修改下代码,调用[Student alloc]
,然后看下运行结果。
---- 0x10c08af60 +[Person(Test2) initialize]
---- 0x10c08afb0 +[Student(Test1) initialize]
这里发现,Student
调用+initialize
方法时是会先调用父类的+initialize
方法(如果代码中没有写父类的调用代码)。
然后我们继续新建一个HighSchoolStudent继承Student,什么都不实现,同时调用[HighSchoolStudent alloc]
,查看结果。
---- 0x101070048 +[Person(Test2) initialize]
---- 0x101070098 +[Student(Test1) initialize]
---- 0x10106fff8 +[Student(Test1) initialize]
确实这样会调用两次+[Student(Test1) initialize]
方法。
[HighSchoolStudent initialize]
的调用,是该类对象通过isa
指针找到元类对象,在元类已缓存的方法列表中查方法,如果有就调用;如果没有就查找方法列表,如果还没有找到,那么就去类的父类的元类对象中查找,找到就调用。如果没有就递归下去直到NSObject的元类对象。所以找到了Student
的元类对象,执行了initialize
方法。
源码分析
我们可以在Runtime的源码中,分析一下调用+initialize
方法的实现。首先一个类要调用方法时肯定是调用类方法,那么就先从class_getInstanceMethod
方法入手查看吧。
/***********************************************************************
* class_getInstanceMethod. Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
这里可以看到基本上就是调用了lookUpImpOrNil
函数,而且函数列表中有注释标记了第四个参数/*initalize*/
。那我们继续看。
/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
然后进入lookUpImpOrForward
函数中继续追踪,该方法有些长,只截取了有关的部分代码。
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// other code ..
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// other code ..
}
这里判断如果需要初始化并且该类没有初始化,那么就进行类初始化。我们发现,我们从入口到这里找的话,其实传递进来的initialize
其实是NO
,不会进入初始化,但是咱们找到的地方是对的,我们继续来看代码,了解完实现之后,我们继续看哪里调用该函数时传递的initialize
的值是YES
。接下来我们进入_class_initialize
函数,代码依旧很长,只截取了相关的部分代码。
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
// other code ..
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
// other code ..
}
在这里我们看到,首先如果父类存在并且父类没有初始化过,那么调用_class_initialize
函数来初始化父类,直到整条集成链上的所有类都初始化完毕。之后调用callInitialize
函数来初始化自己。
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
这里很熟悉了,通过消息机制调用initialize
方法。到这里能解释了,为什么initialize
会调用父类的initialize
方法。
回来我们继续说一下什么时候调用lookUpImpOrForward
是传递的initialize
的值为YES
_class_lookupMethodAndLoadCache3
方法,最终调用是汇编层调用,也就是说我们的代码调用,代码调用前是需要这个类初始化完毕的。这里就是我们要找的入口。objc_loadWeakRetained
、weak_register_no_lock
因为这两个方法会调用SEL_retainWeakReference
这个selector,所以要判断是否已经初始化完毕。methodForSelector
、methodFor
、instanceMethodFor
这三个方法也是获取IMP
的,所以也是需要这个类初始化完毕,否则通过Runtime增加的一些方法,可能无法获取到。
总结
本文主要通过官方文档、例子以及Runtime源码,分析了+initialize
方法的调用,总结如下:
- 当代码执行到一个类第一次调用方法时,会调用这个类的
+initialize
方法 - 在调用自身类的
+initialize
方法之前,会判断其父类链上是否有类还没有执行+initialize
方法,如果没有执行,那么执行。所以所有父类的+initialize
方法都执行在前,子类的+initialize
执行在后。 - 如果一个类有多个分类都实现了
+initialize
方法,那么会执行编译顺序的最后一个分类实现的+initialize
方法 - 当一个类实现了
+initialize
方法,但是子类没有实现+initialize
或者子类在实现+initialize
方法中显式的调用的[super initialize]
方法,那么该类的+initialize
方法会调用多次,如果不想该方法被多次调用,可以在该类的+initialize
方法通过if (self = [ClassName self])
进行判断来避免多次调用。
+load
方法与+initialize
方法的区别
调用方式
+load
方法
根据函数地址直接调用
+initialize
方法
是通过objc_msgSend
调用
调用时机
+load
方法
是Runtime加载类、分类的时候调用(如果不显式调用,只会调用一次)
+initialize
方法
是类第一次接收到消息的时候调用(如果不显式调用,可能存在调用多次的风险)
调用顺序
+load
方法
- 先调用类的
+load
方法,再调用分类的+load
方法 - 有继承关系的类,先调用父类的
+load
,后调用子类的+load
方法 - 没有继承关系的类,会按照编译顺序来执行
+load
方法 - 所有的分类,都按照编译顺序来执行
+load
方法
+initialize
方法
- 先调用父类的
+initialize
方法,后调用子类的+initialize
方法 - 如果一个类有分类,那么会调用最后编译的分类实现的
+initialize
方法 - 通过消息机制调用,当子类没有实现
+initialize
方法时,会调用父类的initialize
方法