initialize方法什么时候调用?
首先我们创建一个TPerson类:
@interface TPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "TPerson.h"
@implementation TPerson
+ (void)initialize {
NSLog(@"类-initialize");
}
@end
然后我们在main函数中调用以下方法:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "TPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool{
Class cls = [TPerson class];
NSLog(@"---class----");
TPerson *per = [TPerson alloc];
NSLog(@"---alloc----");
}
return 0;
}
运行代码,我们在main函数打一个断点,可以看到在进入main函数之前控制台并没有输出,说明initialize是在运行时调用。跳过断点,控制台输出:
2020-02-18 18:08:23.696091+0800 objc-debug[31356:847168] 类-initialize
2020-02-18 18:08:23.696411+0800 objc-debug[31356:847168] ---class----
2020-02-18 18:08:23.696592+0800 objc-debug[31356:847168] ---alloc----
从结果,我们可以猜测,initialize的调用和class方法、或者alloc并没有直接关系。然而,当我们调用TPerson的某一个方法的时候,就会触发initialize的调用,联想到方法的调用本质就是发送消息,我们可以在lookUpImpOrForward查探一下究竟。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
}
根据代码我们可以判断,此处大概和initialize相关,设置一个断点,在调用[TPerson class]这个方法之后果然来了这里,跟踪代码:
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
assert(cls->isRealized());
if (cls->isInitialized()) {
if (!leaveLocked) lock.unlock();
return cls;
}
。。。。。。
initializeNonMetaClass(nonmeta);
return cls;
}
void initializeNonMetaClass(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
.......
// 确保在initialize类的时候,其父类已经在initializing了
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// 试着将类的初始化状态自动设置为 CLS_INITIALIZING.
{
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
callInitialize(cls);
}
......
}
void setInitializing() {
ISA()->setInfo(RW_INITIALIZING);
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
}
顺着代码,我们发现,如果类没有initialize,就会以IMP的方式调用。
然后我们保留class方法,注释掉alloc方法
Class cls = [TPerson class];
NSLog(@"---class----");
// TPerson *per = [TPerson alloc];
// NSLog(@"---alloc----");
// 控制台输出
2020-02-18 19:04:41.693528+0800 objc-debug[31838:896818] 类-initialize
2020-02-18 19:04:41.693962+0800 objc-debug[31838:896818] ---class----
然后我们注释class方法,保留alloc方法:
// Class cls = [TPerson class];
// NSLog(@"---class----");
TPerson *per = [TPerson alloc];
NSLog(@"---alloc----");
// 控制台输出
2020-02-18 19:05:06.043299+0800 objc-debug[31855:897517] 类-initialize
2020-02-18 19:05:06.043415+0800 objc-debug[31855:897517] ---alloc----
这也验证了我们的推测,initialize的调用确实和哪一个方法没有关系,只要是类第一次调用方法就会调用。
分类的initialize方法
那么分类如果也实现了initialize方法,什么时候调用呢?
并且为其创建一个分类:
#import "TPerson.h"
@interface TPerson (addition)
@end
@implementation TPerson (addition)
+ (void)initialize {
NSLog(@"分类-initialize");
}
@end
运行程序,只看到控制台输出:
2020-02-18 19:33:43.443330+0800 objc-debug[31951:908425] 分类-initialize
这是因为分类方法都是通过attachList方法加入到类的ro或者rw中,而attachList方法是先将原来的列表进行扩容,然后将旧数据移动到列表的尾部位置,再把新的数据copy到列表头部位置。所以分类的方法在前面,主类方法在后面,方法查找的时候找到前面的方法就直接返回了,并不会再去调用主类的方法。
总结
initialize只会调用一次,在对类第一次发送消息的时候调用。
主类和分类都有initialize方法,只会调用分类的方法。