在这之前,我从没有想过,+load和+initialize能扯出这么多东西来,但今天确实扯出这么多,如有错误之处,欢迎指正哈~~~
+load 方法是系统自动调用的,无需手动调用,系统自动为每一个类调用+load方法(如果有),所以也无需手动调用[super load]方法。 +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的顺序加载。 +load 方法是在所有类被加入到runtime以后调用的。 [ChildClass load]方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。 在所有类的+load方法调用完以后再调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。 为了方便阅读,我将console中的输出时间全部去掉了,
学习的开始,首先我们新建工程一个LoadAndInitializeTest项目
一、+load 方法是系统自动调用的,无需手动调用,系统自动为每一个类调用+load方法(如果有),所以也无需手动调用[super load]方法。 在Xcode中新建文件夹(object),然后新建一个NSObject的子类MyObject。 在MyObject的.m文件中加入以下代码,然后run +(void)load { NSLog(@"%s",FUNCTION); } 打印输出
LoadAndInitializeTest[27822:1740708] +[MyObject load] 我们并没有手动调用MyObject的任何方法,但是+load方法确实调用了,所以+load 方法是系统自动调用的,无需手动调用。
在Xcode中新建文件夹(super),然后新建一个NSObject的子类MyObjectSuper, 然后将MyObject的父类改成MyObjectSuper。 并在MyObjectSuper.m中输入以下代码,然后run +(void)load { NSLog(@"%s",FUNCTION); } 打印输出
LoadAndInitializeTest[31059:1753828] +[MyObjectSuper load] LoadAndInitializeTest[31059:1753828] +[MyObject load] 可见,父类的 +load方法也是自动加载的,无需手动调用。
在[MyObject load]中添加 [super load],然后run 打印输出 LoadAndInitializeTest[33168:1761400] +[MyObjectSuper load] LoadAndInitializeTest[33168:1761400] +[MyObjectSuper load] LoadAndInitializeTest[33168:1761400] +[MyObject load] 可见 [MyObjectSuper load] 被调用了两次,也说明了[SuperClass load]方法也是自动加载的,无需手动调用。 为了安全起见,在+load中一定要做唯一性判断,一般使用以下代码。
- (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ doSomething }); } 我们再多做一个测试,如果某个程序员在[ChildClass load]中,手贱写一个[super load],而[SuperClass load]的职责是利用黑魔法进行方法交换,[SuperClass load]就会调用两次,方法交换了两次,就等于没有交换了,如果不懂+load方法的使用,像这个的bug,我们却很难发现。 在父类(MyObjectSuper)中添加以下代码 在此只是为了演示+load方法,关于黑魔法的坑就不在此详解了
#import "MyObjectSuper.h" #import <objc/runtime.h>
@implementation MyObjectSuper
- (void)load{ NSLog(@"%s",FUNCTION); Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc")); Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle)); method_exchangeImplementations(method1, method2); }
-(void)dealloc { NSLog(@"%s",FUNCTION); }
- (void)deallocSwizzle{ NSLog(@"%s",FUNCTION); [self deallocSwizzle]; }
@end 在子类(MyObject)中添加以下代码, 注意这一行代码:[super load];
#import "MyObject.h"
@implementation MyObject
- (void)load { [super load]; NSLog(@"%s",FUNCTION); } @end 我们在其他地方创建并销毁对象,然后run
- (void)viewDidLoad { [super viewDidLoad]; [[MyObject alloc] init]; } 打印输出
LoadAndInitializeTest[74856:1942900] +[MyObjectSuper load] LoadAndInitializeTest[74856:1942900] +[MyObjectSuper load] LoadAndInitializeTest[74856:1942900] +[MyObject load] LoadAndInitializeTest[74856:1942900] -[MyObjectSuper dealloc] 分析结果:[MyObjectSuper load]被调用了两次,方法被交换了两次,等于没有交换,所以对象销毁时,调用[MyObjectSuper dealloc],而没有调用[MyObjectSuper deallocSwizzle]。
那我们现在把子类里的[super load];注释掉,其他代码不做修改,然后run 打印输出
LoadAndInitializeTest[76655:1950334] +[MyObjectSuper load] LoadAndInitializeTest[76655:1950334] +[MyObject load] LoadAndInitializeTest[76655:1950334] -[MyObjectSuper deallocSwizzle] LoadAndInitializeTest[76655:1950334] -[MyObjectSuper dealloc] 哈哈~~~,这样就正常了, 我们一定要知道,+load方法可能会被调用多次,在load方法中,我们必须做判断,因为总有一个程序员会继承你的类,然后在load方法中调用[super load]。
现在我们在父类中的修改代码如下
-
(void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"%s",FUNCTION); Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc")); Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle)); method_exchangeImplementations(method1, method2); }); } 子类代码如下,然后run
-
(void)load { [super load]; NSLog(@"%s",FUNCTION); } 打印输出
LoadAndInitializeTest[76655:1950334] +[MyObjectSuper load] LoadAndInitializeTest[76655:1950334] +[MyObject load] LoadAndInitializeTest[76655:1950334] -[MyObjectSuper deallocSwizzle] LoadAndInitializeTest[76655:1950334] -[MyObjectSuper dealloc] 这下是真的正常了,我们再也不怕子类+load方法中被调用[super load]了
我在阅读一些知名的第三库时,如AFNetworking、以及小码哥的MJRefresh时,确实发现一些不严谨的做法,虽然并不是严重的bug,但是总归是隐患,代码如下 AFNetworking 代码,只贴一小部分了 @implementation _AFURLSessionTaskSwizzling
- (void)load { if (NSClassFromString(@"NSURLSessionTask")) { 我创建了一个_AFURLSessionTaskSwizzling的子类, 在子类中重新+load方法,然后[super load]; 发现程序确实能调到这里 } } 小码哥的 MJRefresh 的load @implementation UIViewController (Example)
- (void)load { Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc")); Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle)); method_exchangeImplementations(method1, method2); } 在滴滴出行客户端里是这样写的,可见我大滴滴早就注意到这个问题了
@implementation BaseViewController (categroy)
- (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //方法交换 }); } 二、+load 方法按照[SuperClass load]->[Class load]->[ChildClass load]->顺序加载的。 MyObjectSuper和MyObject注释掉无用信息,只保留以下代码
- (void)load { NSLog(@"%s",FUNCTION); } @end 我们新建child文件夹,并在child文件夹下创建MyObject的子类MyObjectChild,添加如下代码,然后run
#import "MyObjectChild.h" @implementation MyObjectChild
- (void)load { NSLog(@"%s",FUNCTION); } @end 打印输出
LoadAndInitializeTest[9937:2079123] +[MyObjectSuper load] LoadAndInitializeTest[9937:2079123] +[MyObject load] LoadAndInitializeTest[9937:2079123] +[MyObjectChild load] 可见+load方法的调用顺序是从父类开始,然后子类,再子类, 我尝试一下更改Compile Sources 里的顺序,结果依然如此,证明了+load方法的调用顺序是从父类顺序到子类的。
三、+load 方法是在所有类被加入到runtime以后调用的。 在分享这个问题之前,我们先来看一小段Apple关于+load的文档
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
对于此段文档,大神朱晓辉是这么翻译的
当类(Class)或者类别(Category)加入Runtime中时(就是被引用的时候)。实现该方法,可以在加载时做一些类特有的操作。
而我是这么翻译的
当类或者类别加入到Runtime中以后,实现该方法可以在加载时做一些类特有的操作。
以上两段翻译根本不同点是“时”和“以后”,我认为“时”,是正在进行时,是正在添加。而“以后”是Add操作成功以后的事,是一种完成时态。 现在我们就来测一下,到底是怎么回事!
修改MyObject.h如下 遵守了一个NSObject的协议,添加了两个property属性
@interface MyObject : MyObjectSuper @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @end 修改 MyObject.m 如下,有点多,大家自己看吧
#import <objc/runtime.h>
@implementation MyObject { int nIval;//第一处增加 }
-
(void)load { NSLog(@"%s",FUNCTION);
//第二处增加 NSLog(@"-1.------华丽的风格下-------"); unsigned int count = 0; Class metaClass = object_getClass([MyObject class]); Method *methods = class_copyMethodList(metaClass, &count); for (int i = 0; i < count; i++) { NSLog(@"类方法为:%s", sel_getName(method_getName(methods[i]))); } free(methods);
NSLog(@"-2.------华丽的风格下------"); unsigned int countMethod = 0; methods = class_copyMethodList([self class], &countMethod); for (int i = 0; i < countMethod; i++) { NSLog(@"实例方法为:%s", sel_getName(method_getName(methods[i]))); } free(methods);
NSLog(@"-3.------华丽的风格下-------"); unsigned int countIval = 0; Ivar *ivals = class_copyIvarList([self class], &countIval); for (int i = 0; i < countIval; i++) { NSLog(@"变量为:%s", ivar_getName(ivals[i])); } free(ivals);
NSLog(@"-4.------华丽的风格下------"); unsigned int countProperty = 0; objc_property_t *propertys = class_copyPropertyList([self class], &countProperty); for (int i = 0; i < countProperty; i++) { NSLog(@"属性为:%s", property_getName(propertys[i])); } free(propertys);
NSLog(@"-5.------华丽的风格下------"); unsigned int countProtocol = 0; __unsafe_unretained Protocol **protocols = class_copyProtocolList([self class], &countProtocol); for (int i = 0; i < countProtocol; i++) { NSLog(@"协议为:%s", protocol_getName(protocols[i])); } NSLog(@"------华丽的风格下------");
MyObject *myObject = [[MyObject alloc] init]; myObject.age = 18; myObject.name = @"司晓刚"; NSLog(@"myObject.name=%@,myObject.age=%ld",myObject.name, myObject.age); }
- (void)objectFunction {//第三处增加 NSLog(@"%s",FUNCTION); }
- (void)classFunction {//第四处增加 NSLog(@"%s",FUNCTION); } @end 现在你可以多想一想,你心中认为的和真实输出是否一致 打印输出
LoadAndInitializeTest[33804:2175226] +[MyObjectSuper load] LoadAndInitializeTest[33804:2175226] +[MyObject load] LoadAndInitializeTest[33804:2175226] -1.------华丽的风格下------ LoadAndInitializeTest[33804:2175226] 类方法为:classFunction LoadAndInitializeTest[33804:2175226] 类方法为:load LoadAndInitializeTest[33804:2175226] -2.-------华丽的风格下----- LoadAndInitializeTest[33804:2175226] 实例方法为:objectFunction LoadAndInitializeTest[33804:2175226] 实例方法为:.cxx_destruct LoadAndInitializeTest[33804:2175226] 实例方法为:name LoadAndInitializeTest[33804:2175226] 实例方法为:setName: LoadAndInitializeTest[33804:2175226] 实例方法为:setAge: LoadAndInitializeTest[33804:2175226] 实例方法为:age LoadAndInitializeTest[33804:2175226] -3.-------华丽的风格下----- LoadAndInitializeTest[33804:2175226] 变量为:nIval LoadAndInitializeTest[33804:2175226] 变量为:_name LoadAndInitializeTest[33804:2175226] 变量为:_age LoadAndInitializeTest[33804:2175226] -4.-------华丽的风格下----- LoadAndInitializeTest[33804:2175226] 属性为:name LoadAndInitializeTest[33804:2175226] 属性为:age LoadAndInitializeTest[33804:2175226] 属性为:hash LoadAndInitializeTest[33804:2175226] 属性为:superclass LoadAndInitializeTest[33804:2175226] 属性为:description LoadAndInitializeTest[33804:2175226] 属性为:debugDescription LoadAndInitializeTest[33804:2175226] -5.-------华丽的风格下----- LoadAndInitializeTest[33804:2175226] 协议为:NSObject LoadAndInitializeTest[33804:2175226] ------华丽的风格下--------- LoadAndInitializeTest[33804:2175226] myObject.name=司晓刚,myObject.age=18 LoadAndInitializeTest[33804:2175226] +[MyObjectChild load] 前两条和最后一条+load方法,我们都能看明白了,直接忽视就好。
第一条华丽的分割线打印的是类方法, 我们成功的打印出classFunction、load。
第二条华丽的分割线打印的是实例方法, objectFunction首先被打印出来, “.cxx_destruct”是什么,自己去研究吧, 属性name和属性age的set、get方法被打印出来。
第三条华丽的分割线打印的是变量, nIval是我们自己直接定义的, _name和_age是属性name和age自动为我们生成的带有下划线的变量。
第四条华丽的分割线打印的是属性 name和age就不用解释了 superclass、description、debugDescription也不解释了,自己研究吧。
第五条华丽的分割线打印的是协议 NSObject 协议是我在头文件中添加的。
到这儿,最重要的一点开始了, 我实例化了一个MyObject对象,并且给他赋值,然后成功的打印出来了, 这就说明,我们这个类以及完全可以正常使用了,难道这还不能说明类的+load方法是在类加载完成以后才调用的吗?如果正在加载的话,我们能完整的得到类的这么多信息和使用类吗?
很显然,我认为我是对的,如有错误之处,欢迎指正
让我们重新回到Apple的那段文档,开始下一个问题
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
Apple说,当一个类或者分类被加入到runtime以后被调用,我以前一直认为当把一个类加入到runtime以后,立刻调用它的+load方法,然后再去加载它的兄弟类或者子类,也就是说,我在+load方法中,去获取它子类的信息或者实例化子类,都不会成功的,因为类还没有加入到runtime中。
子类上面已经说过了,它在父类的+load后加载
修改MyObjectChild.h 如下
@interface MyObjectChild : MyObject @property (nonatomic, copy) NSString *nameChild; @property (nonatomic, assign) NSInteger ageChild; @end 修改MyObjectChild.m 如下
@implementation MyObjectChild{ int nIvalChild; }
- (void)load { NSLog(@"%s",FUNCTION); }
-(void)objectFunctionChild { NSLog(@"%s",FUNCTION); }
-
(void)classFunctionChild { NSLog(@"%s",FUNCTION); } @end 在[MyObject load]最后添加以下代码
NSLog(@"------以下是子类华丽的分割线------");
NSLog(@"-1.------华丽的风格下Child------"); MyObjectChild *myObjectChild = [[MyObjectChild alloc] init]; myObjectChild.age = 18; myObjectChild.name = @"司晓刚"; NSLog(@"myObjectChild.name=%@,myObjectChild.age=%ld",myObjectChild.name, myObjectChild.age);
count = 0; metaClass = object_getClass([MyObjectChild class]); methods = class_copyMethodList(metaClass, &count); for (int i = 0; i < count; i++) { NSLog(@"Child类方法为:%s", sel_getName(method_getName(methods[i]))); } free(methods);
NSLog(@"-2.------华丽的风格下Child------"); countMethod = 0; methods = class_copyMethodList([myObjectChild class], &countMethod); for (int i = 0; i < countMethod; i++) { NSLog(@"Child实例方法为:%s", sel_getName(method_getName(methods[i]))); } free(methods);
NSLog(@"-3.------华丽的风格下Child-------"); countIval = 0; ivals = class_copyIvarList([myObjectChild class], &countIval); for (int i = 0; i < countIval; i++) { NSLog(@"Child变量为:%s", ivar_getName(ivals[i])); } free(ivals);
NSLog(@"-4.------华丽的风格下Child------"); countProperty = 0; propertys = class_copyPropertyList([myObjectChild class], &countProperty); for (int i = 0; i < countProperty; i++) { NSLog(@"Child属性为:%s", property_getName(propertys[i])); } free(propertys);
NSLog(@"-5.------华丽的风格下Child-------"); countProtocol = 0; protocols = class_copyProtocolList([myObjectChild class], &countProtocol); for (int i = 0; i < countProtocol; i++) { NSLog(@"Child协议为:%s", protocol_getName(protocols[i])); } NSLog(@"------华丽的风格下Child------"); 输出打印
LoadAndInitializeTest[55040:2254257] ------以下是子类华丽的分割线------ LoadAndInitializeTest[55040:2254257] -1.------华丽的风格下Child------ LoadAndInitializeTest[55040:2254257] myObjectChild.name=司晓刚,myObjectChild.age=18 LoadAndInitializeTest[55040:2254257] Child类方法为:classFunctionChild LoadAndInitializeTest[55040:2254257] Child类方法为:load LoadAndInitializeTest[55040:2254257] -2.------华丽的风格下Child------- LoadAndInitializeTest[55040:2254257] Child实例方法为:objectFunctionChild LoadAndInitializeTest[55040:2254257] Child实例方法为:nameChild LoadAndInitializeTest[55040:2254257] Child实例方法为:setNameChild: LoadAndInitializeTest[55040:2254257] Child实例方法为:ageChild LoadAndInitializeTest[55040:2254257] Child实例方法为:setAgeChild: LoadAndInitializeTest[55040:2254257] Child实例方法为:.cxx_destruct LoadAndInitializeTest[55040:2254257] -3.------华丽的风格下Child------- LoadAndInitializeTest[55040:2254257] Child变量为:nIvalChild LoadAndInitializeTest[55040:2254257] Child变量为:_nameChild LoadAndInitializeTest[55040:2254257] Child变量为:_ageChild LoadAndInitializeTest[55040:2254257] -4.------华丽的风格下Child------ LoadAndInitializeTest[55040:2254257] Child属性为:nameChild LoadAndInitializeTest[55040:2254257] Child属性为:ageChild LoadAndInitializeTest[55040:2254257] Child属性为:hash LoadAndInitializeTest[55040:2254257] Child属性为:superclass LoadAndInitializeTest[55040:2254257] Child属性为:description LoadAndInitializeTest[55040:2254257] Child属性为:debugDescription LoadAndInitializeTest[55040:2254257] -5.------华丽的风格下Child------- LoadAndInitializeTest[55040:2254257] Child协议为:NSObject 这些输出,我就不解释了,输出证明了,在父类的+load中已经能获取到子类的信息并且可以实例化子类了。
我证明的是把所有的类都加入到runtime以后,然后开始调用+load方法,而不是Apple说的一个类,对于这一点,挑战性过大,请大神指正。
四、+load方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。 在object文件夹中创建两个MyObjectSuper的子类MyObject1和MyObject2, 在child文件夹中创建一个MyObject的子类MyObjectChild1 并且在三个.m里实现以下代码,然后run
- (void)load { NSLog(@"%s",FUNCTION); } 输出打印
LoadAndInitializeTest[75083:2339602] +[MyObjectSuper load] LoadAndInitializeTest[75083:2339602] +[MyObject load] LoadAndInitializeTest[75083:2339602] +[MyObject1 load] LoadAndInitializeTest[75083:2339602] +[MyObject2 load] LoadAndInitializeTest[75083:2339602] +[MyObjectChild load] LoadAndInitializeTest[75083:2339602] +[MyObjectChild1 load] 我们现在去查看以下Compile Sources,并且截图如下
屏幕快照 2018-11-24 下午11.18.26.png 我们发现Compile Sources里的顺序竟然与我们打印的顺序惊人的一致,难道真的是这样吗? 我们随意拖动Compile Sources的排列顺序,然后run
屏幕快照 2018-11-24 下午11.28.24.png
打印输出
LoadAndInitializeTest[78440:2353132] +[MyObjectSuper load] LoadAndInitializeTest[78440:2353132] +[MyObject load] LoadAndInitializeTest[78440:2353132] +[MyObjectChild1 load] LoadAndInitializeTest[78440:2353132] +[MyObjectChild load] LoadAndInitializeTest[78440:2353132] +[MyObject2 load] LoadAndInitializeTest[78440:2353132] +[MyObject1 load] Compile Sources 里的第一个类是MyObjectChild1, 在调用[MyObjectChild1 load]之前会先调用其父类[MyObject load], 在调用[MyObject load]之前会调用其父类[MyObjectSuper load], 所以,Compile Sources 里第一个类就打印出来前三个+load方法, LoadAndInitializeTest[78440:2353132] +[MyObjectSuper load] LoadAndInitializeTest[78440:2353132] +[MyObject load] LoadAndInitializeTest[78440:2353132] +[MyObjectChild1 load] Compile Sources 里的第二个类是MyObjectChild, 在调用[MyObjectChild load]之前会先调用其父类[MyObject load],因为父类的+load方法已经被调用,所以无需再调用。 在调用[MyObject load]之前会调用其父类[MyObjectSuper load],因为父类的+load方法已经被调用,所以无需再调用。 所以,Compile Sources 里第二个类就打印出来第四个+load方法, LoadAndInitializeTest[78440:2353132] +[MyObjectChild load] [MyObject2 load]和[MyObject1 load]完全跟以上一致原理,请自行推理。 事实上,Apple的文档是这样写的
A class’s +load method is called after all of its superclasses’ +load methods. 一个类的+load方法在调用前,会先调用其父类的+load。 我们实验得出的结论与Apple的文档是一致的,如果Apple文档再加上,类的+load方法按照Compile Sources里顺序调用的,两条规则合并起来就完美了。
五、在所有类的+load方法调用完以后再去调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。 我们现在创建一系列的分类,如下,并分别实现其+load方法,然后run @interface MyObjectSuper (superCategory0) @interface MyObjectSuper (superCategory1)
@interface MyObject (category0) @interface MyObject (category1) @interface MyObject1 (category0) @interface MyObject1 (category1) @interface MyObject2 (category0) @interface MyObject2 (category1)
@interface MyObjectChild (category0) @interface MyObjectChild (category1) @interface MyObjectChild1 (category0) @interface MyObjectChild1 (category1)
打印输出
LoadAndInitializeTest[90277:2399103] +[MyObjectSuper load] LoadAndInitializeTest[90277:2399103] +[MyObject load] LoadAndInitializeTest[90277:2399103] +[MyObjectChild1 load] LoadAndInitializeTest[90277:2399103] +[MyObjectChild load] LoadAndInitializeTest[90277:2399103] +[MyObject2 load] LoadAndInitializeTest[90277:2399103] +[MyObject1 load] LoadAndInitializeTest[90277:2399103] +[MyObjectSuper(superCategory0) load] LoadAndInitializeTest[90277:2399103] +[MyObject(category1) load] LoadAndInitializeTest[90277:2399103] +[MyObject(category0) load] LoadAndInitializeTest[90277:2399103] +[MyObject2(category1) load] LoadAndInitializeTest[90277:2399103] +[MyObject2(category0) load] LoadAndInitializeTest[90277:2399103] +[MyObjectChild(category1) load] LoadAndInitializeTest[90277:2399103] +[MyObjectChild1(category0) load] LoadAndInitializeTest[90277:2399103] +[MyObject1(category0) load] LoadAndInitializeTest[90277:2399103] +[MyObjectChild(category0) load] LoadAndInitializeTest[90277:2399103] +[MyObject1(category1) load] LoadAndInitializeTest[90277:2399103] +[MyObjectChild1(category1) load] LoadAndInitializeTest[90277:2399103] +[MyObjectSuper(superCategory1) load] 我们发现,类的+load方法全部调用完以后才会调用[category load]方法。 我们现在去修改category文件在Compile Sources里的顺序,我们会很容易发现,Compile Sources里的顺序与我们输出的顺序,总是完全一致。
Apple的文档上是这么写的
A category +load method is called after the class’s own +load method. 一个Category的+load方法在其所属类的+load方法之后调用 苹果的这段文档,我不能说他不对,但是我得到的结论是,[category load]的调用是在所有类的+load之后,而不是特定的类(the class)之后。 如有错误,欢迎指正。
我们再做一个有趣的测试 我们修改main.m文件如下,然后 run
int main(int argc, char * argv[]) { @autoreleasepool { NSLog(@"main"); return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 输出打印
... LoadAndInitializeTest[95236:2418416] +[MyObjectChild(category0) load] LoadAndInitializeTest[95236:2418416] +[MyObject1(category1) load] LoadAndInitializeTest[95236:2418416] +[MyObjectChild1(category1) load] LoadAndInitializeTest[95236:2418416] +[MyObjectSuper(superCategory0) load] LoadAndInitializeTest[95236:2418416] +[MyObjectSuper(superCategory1) load] LoadAndInitializeTest[95236:2418416] main main 竟然是最后输出的,说明了所有的load方法都是先于main函数被调用的。
现在我对+load的使用进行总结:
一、+load 方法是在所有类被加入到runtime以后,main函数执行之前被系统自动调用的。 二、系统自动为每一个类调用+load方法(如果有),无需手动调用,也无需手动调用[super load]。 三、+load方法会按照文件所在的Compile Sources顺序加载,在调用类的+load之前,会优先调用其父类的+load方法。 四、在所有类的+load方法调用完以后再调用[Category load]方法,加载顺序按照Compile Sources排列顺序。 +initialize: 我们在程序中有很多类似于以下的代码,我们称为懒加载, 首先
@property (nonatomic, strong) UILabel *myLabel; 然后
- (UILabel *)myLabel { if (!_myLabel) { _myLabel = [[UILabel alloc] init]; ... } return _myLabel; } 最后
[self addSubview:self.myLabel]; 这种懒加载的方式是,直到第一次向myLabel发送消息时,才会创建myLabel对象。 +initialize方法也是类似的原理,在类第一次接收消息时被调用。
事实上,Apple的文档是这么写的,也就是说,他总在用户调用之前调用。
Initializes the class before it receives its first message. 在这个类接收第一条消息之前调用。
关于+initialize方法,我总结如下
一、+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。 二、在调用子类的+ initialize 方法之前,会先调用父类的+ initialize 方法(如果有),所以也无需手动调用[super initialize]方法。 三、如果父类中有+ initialize方法,而子类中没有+ initialize方法,子类会自动继承父类的+ initialize方法,也就是说父类的+ initialize方法会调用两次。 四、Category中+ initialize方法会覆盖类中的+ initialize,同一个类有多个Category都实现了+initialize方法时,Compile Sources 列表中最后一个Category 的+initialize方法会覆盖其他的+ initialize方法。 一、+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。 在MyObjectChild里添加如下代码,然后run
- (instancetype)init { NSLog(@"init"); if (self = [super init]) { } return self; }
- (void)initialize { NSLog(@"%s",FUNCTION); } 然后无任何+ initialize输出
我们现在在Controller中添加如下代码,然后run
#import "MyObjectChild.h"
@implementation ViewController
-
(void)viewDidLoad { [super viewDidLoad];
NSLog(@"[[MyObjectChild alloc] init]; 之前"); [[MyObjectChild alloc] init]; NSLog(@"[[MyObjectChild alloc] init]; 之后"); } 输出打印
LoadAndInitializeTest[98430:2830873] [[MyObjectChild alloc] init]; 之前 LoadAndInitializeTest[98430:2830873] +[MyObjectChild initialize] LoadAndInitializeTest[98430:2830873] init LoadAndInitializeTest[98430:2830873] [[MyObjectChild alloc] init]; 之后 +initialize 打印信息被输出了,这就说明了+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。
二、在调用子类的+ initialize 方法之前,会先调用父类的+ initialize 方法(如果有),所以也无需手动调用[super initialize]方法。 在MyObject、MyObjectSuper分别加入以下代码,然后run
- (void)initialize { NSLog(@"%s",FUNCTION); } 打印输出
LoadAndInitializeTest[30644:2557942] +[MyObjectSuper initialize] LoadAndInitializeTest[30644:2557942] +[MyObject initialize] LoadAndInitializeTest[30644:2557942] +[MyObjectChild initialize] 从父类到子类依次被打印出来,说明+ initialize与+load方法一样,在调用子类的方法时,会先调用父类的方法。 现在我们在MyObjectChild里加入以下代码,然后run
[super initialize]; 打印输出
LoadAndInitializeTest[33679:2569542] +[MyObjectSuper initialize] LoadAndInitializeTest[33679:2569542] +[MyObject initialize] LoadAndInitializeTest[33679:2569542] +[MyObject initialize] LoadAndInitializeTest[33679:2569542] +[MyObjectChild initialize] 这说明子类中无需手动调用[super initialize]方法。
三、如果父类实现了+ initialize方法,而子类没有实现+ initialize,子类会自动继承父类的+ initialize,也就是说,父类的+initialize方法,会被自动调用两次, 现在我们注释掉MyObjectChild、MyObject 的+initialize,然后run 打印输出
LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize] LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize] LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize] 我的天啊,[MyObjectSuper initialize]竟然被打印了三次, 因为MyObject会继承父类的+ initialize方法, 而MyObjectChild也会继承父类的+ initialize方法, 所以他们都继承了MyObjectSuper的+ initialize方法,所以打印了三次。
在这我特别说明一点,+ initialize从名字上看,是初始化函数,我们就会认为只调用一次,而且其他很多博客里都明确说明+ initialize只调用一次,但事实上,他确实会自动调用多次,如果我这有错误之处,还希望能给指正。
因为+ initialize方法会被自动继承,所以,+ initialize的出错率要比+load更大一些。
那+initialize方法里到底该怎么写,我知道的通常有两种办法, 第一种
-
(void)initialize{ if (self == [MyClass class]) { .... } } 第二种
-
(void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ ... }); } 四、Category中+ initialize方法会覆盖类中的+ initialize,同一个类有多个Category都实现了+initialize方法时,Compile Sources 列表中最后一个Category 的+initialize方法会覆盖其他的+ initialize方法。 在@implementation MyObjectChild (category0)中添加以下代码,然后run
-
(void)initialize { NSLog(@"%s",FUNCTION); } 输出打印
LoadAndInitializeTest[64871:2690309] +[MyObjectSuper initialize] LoadAndInitializeTest[64871:2690309] +[MyObject initialize] LoadAndInitializeTest[64871:2690309] +[MyObjectChild(category0) initialize] category0 里的+ initialize覆盖了类里的+ initialize。
在@implementation MyObjectChild (category1)中添加以下代码,然后run
- (void)initialize { NSLog(@"%s",FUNCTION); } 输出打印
LoadAndInitializeTest[66414:2697021] +[MyObjectSuper initialize] LoadAndInitializeTest[66414:2697021] +[MyObject initialize] LoadAndInitializeTest[66414:2697021] +[MyObjectChild(category1) initialize] category1 里的+ initialize又覆盖了category0里的+ initialize。
我们去Compile Sources中查看一下,此时MyObjectChild category1肯定排在category0的后面,我们也可以随意更改排列顺序,Compile Sources中最后一个category肯定覆盖其他所有的+ initialize方法。
我们也可以去修改其父类的category方法,发现父类也同样遵守这样的规则。
作者:xiaogangsi 链接:www.jianshu.com/p/e6979a17d… 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。