在這之前,我從沒有想過,+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 <NSObject>
@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 <NSObject>
@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,并且截圖如下
我們發現Compile Sources里的順序竟然與我們打印的順序驚人的一致,難道真的是這樣嗎?
我們隨意拖動Compile Sources的排列順序,然后run
打印輸出
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方法,發現父類也同樣遵守這樣的規則。
好吧,我這就寫完了,+ initialize的總結與+ initialize開始總結的一模一樣,不重復總結了。
這第一次寫技術文章,難免有不足之處,如果有錯誤之處,還請指正。
如果你已經能看到這里,說明你已經足夠有耐心了,
關于+load,我估計丟掉一個知識點,你知道是什么嗎?
本文所有代碼已經上傳至GitHub
寫本文時,我重點參考了以下兩篇博客
類方法load和initialize的區別
iOS類方法load和initialize詳解