在這之前,我從沒有想過灯萍,+load和+initialize能扯出這么多東西來轧铁,但今天確實(shí)扯出這么多,如有錯(cuò)誤之處旦棉,歡迎指正哈~~~
- +load 方法是系統(tǒng)自動調(diào)用的齿风,無需手動調(diào)用,系統(tǒng)自動為每一個(gè)類調(diào)用+load方法(如果有)绑洛,所以也無需手動調(diào)用[super load]方法救斑。
- +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的順序加載。
- +load 方法是在所有類被加入到runtime以后調(diào)用的真屯。
- [ChildClass load]方法是按照Compile Sources的排列順序加載的脸候,但要遵循調(diào)用[ChildClass load]之前,必須先調(diào)用其[SuperClass load]方法绑蔫。
- 在所有類的+load方法調(diào)用完以后再調(diào)用[Category load]方法运沦,[Category load]的調(diào)用順序完全按照Compile Sources排列順序。
為了方便閱讀配深,我將console中的輸出時(shí)間全部去掉了携添,
學(xué)習(xí)的開始,首先我們新建工程一個(gè)LoadAndInitializeTest項(xiàng)目
一凉馆、+load 方法是系統(tǒng)自動調(diào)用的薪寓,無需手動調(diào)用亡资,系統(tǒng)自動為每一個(gè)類調(diào)用+load方法(如果有),所以也無需手動調(diào)用[super load]方法向叉。
- 在Xcode中新建文件夾(object)锥腻,然后新建一個(gè)NSObject的子類MyObject。
- 在MyObject的.m文件中加入以下代碼母谎,然后run
+(void)load {
NSLog(@"%s",__FUNCTION__);
}
打印輸出
LoadAndInitializeTest[27822:1740708] +[MyObject load]
我們并沒有手動調(diào)用MyObject的任何方法瘦黑,但是+load方法確實(shí)調(diào)用了,所以+load 方法是系統(tǒng)自動調(diào)用的奇唤,無需手動調(diào)用幸斥。
- 在Xcode中新建文件夾(super),然后新建一個(gè)NSObject的子類MyObjectSuper咬扇,
然后將MyObject的父類改成MyObjectSuper甲葬。
并在MyObjectSuper.m中輸入以下代碼,然后run
+(void)load {
NSLog(@"%s",__FUNCTION__);
}
打印輸出
LoadAndInitializeTest[31059:1753828] +[MyObjectSuper load]
LoadAndInitializeTest[31059:1753828] +[MyObject load]
可見懈贺,父類的 +load方法也是自動加載的经窖,無需手動調(diào)用。
- 在[MyObject load]中添加 [super load]梭灿,然后run
打印輸出
LoadAndInitializeTest[33168:1761400] +[MyObjectSuper load]
LoadAndInitializeTest[33168:1761400] +[MyObjectSuper load]
LoadAndInitializeTest[33168:1761400] +[MyObject load]
可見 [MyObjectSuper load] 被調(diào)用了兩次画侣,也說明了[SuperClass load]方法也是自動加載的,無需手動調(diào)用堡妒。
為了安全起見配乱,在+load中一定要做唯一性判斷,一般使用以下代碼皮迟。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
doSomething
});
}
- 我們再多做一個(gè)測試搬泥,如果某個(gè)程序員在[ChildClass load]中,手賤寫一個(gè)[super load]万栅,而[SuperClass load]的職責(zé)是利用黑魔法進(jìn)行方法交換佑钾,[SuperClass load]就會調(diào)用兩次,方法交換了兩次烦粒,就等于沒有交換了休溶,如果不懂+load方法的使用,像這個(gè)的bug扰她,我們卻很難發(fā)現(xiàn)兽掰。
在父類(MyObjectSuper)中添加以下代碼
在此只是為了演示+load方法,關(guān)于黑魔法的坑就不在此詳解了
#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
我們在其他地方創(chuàng)建并銷毀對象孽尽,然后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]
分析結(jié)果:[MyObjectSuper load]被調(diào)用了兩次,方法被交換了兩次忧勿,等于沒有交換杉女,所以對象銷毀時(shí)瞻讽,調(diào)用[MyObjectSuper dealloc],而沒有調(diào)用[MyObjectSuper deallocSwizzle]熏挎。
那我們現(xiàn)在把子類里的[super load];注釋掉速勇,其他代碼不做修改,然后run
打印輸出
LoadAndInitializeTest[76655:1950334] +[MyObjectSuper load]
LoadAndInitializeTest[76655:1950334] +[MyObject load]
LoadAndInitializeTest[76655:1950334] -[MyObjectSuper deallocSwizzle]
LoadAndInitializeTest[76655:1950334] -[MyObjectSuper dealloc]
哈哈~~~坎拐,這樣就正常了烦磁,
我們一定要知道,+load方法可能會被調(diào)用多次哼勇,在load方法中都伪,我們必須做判斷,因?yàn)榭傆幸粋€(gè)程序員會繼承你的類积担,然后在load方法中調(diào)用[super load]陨晶。
- 現(xiàn)在我們在父類中的修改代碼如下
+ (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方法中被調(diào)用[super load]了
- 我在閱讀一些知名的第三庫時(shí)珍逸,如AFNetworking逐虚、以及小碼哥的MJRefresh時(shí)聋溜,確實(shí)發(fā)現(xiàn)一些不嚴(yán)謹(jǐn)?shù)淖龇ǎm然并不是嚴(yán)重的bug叭爱,但是總歸是隱患撮躁,代碼如下
AFNetworking 代碼,只貼一小部分了
@implementation _AFURLSessionTaskSwizzling
+ (void)load {
if (NSClassFromString(@"NSURLSessionTask")) {
我創(chuàng)建了一個(gè)_AFURLSessionTaskSwizzling的子類买雾,
在子類中重新+load方法把曼,然后[super load];
發(fā)現(xiàn)程序確實(shí)能調(diào)到這里
}
}
小碼哥的 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);
}
在滴滴出行客戶端里是這樣寫的,可見我大滴滴早就注意到這個(gè)問題了
@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文件夾下創(chuàng)建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方法的調(diào)用順序是從父類開始僚饭,然后子類震叮,再子類,
我嘗試一下更改Compile Sources 里的順序鳍鸵,結(jié)果依然如此苇瓣,證明了+load方法的調(diào)用順序是從父類順序到子類的。
三偿乖、+load 方法是在所有類被加入到runtime以后調(diào)用的击罪。
在分享這個(gè)問題之前哲嘲,我們先來看一小段Apple關(guān)于+load的文檔
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
對于此段文檔,大神朱曉輝是這么翻譯的
當(dāng)類(Class)或者類別(Category)加入Runtime中時(shí)(就是被引用的時(shí)候)媳禁。實(shí)現(xiàn)該方法撤蚊,可以在加載時(shí)做一些類特有的操作。
而我是這么翻譯的
當(dāng)類或者類別加入到Runtime中以后损话,實(shí)現(xiàn)該方法可以在加載時(shí)做一些類特有的操作侦啸。
以上兩段翻譯根本不同點(diǎn)是“時(shí)”和“以后”,我認(rèn)為“時(shí)”丧枪,是正在進(jìn)行時(shí)光涂,是正在添加。而“以后”是Add操作成功以后的事拧烦,是一種完成時(shí)態(tài)忘闻。
現(xiàn)在我們就來測一下,到底是怎么回事恋博!
修改MyObject.h如下
遵守了一個(gè)NSObject的協(xié)議齐佳,添加了兩個(gè)property屬性
@interface MyObject : MyObjectSuper <NSObject>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
修改 MyObject.m 如下,有點(diǎn)多债沮,大家自己看吧
#import <objc/runtime.h>
@implementation MyObject {
int nIval;//第一處增加
}
+ (void)load {
NSLog(@"%s",__FUNCTION__);
//第二處增加
NSLog(@"-1.------華麗的風(fēng)格下-------");
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.------華麗的風(fēng)格下------");
unsigned int countMethod = 0;
methods = class_copyMethodList([self class], &countMethod);
for (int i = 0; i < countMethod; i++) {
NSLog(@"實(shí)例方法為:%s", sel_getName(method_getName(methods[i])));
}
free(methods);
NSLog(@"-3.------華麗的風(fēng)格下-------");
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.------華麗的風(fēng)格下------");
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.------華麗的風(fēng)格下------");
unsigned int countProtocol = 0;
__unsafe_unretained Protocol **protocols = class_copyProtocolList([self class], &countProtocol);
for (int i = 0; i < countProtocol; i++) {
NSLog(@"協(xié)議為:%s", protocol_getName(protocols[i]));
}
NSLog(@"------華麗的風(fēng)格下------");
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
現(xiàn)在你可以多想一想,你心中認(rèn)為的和真實(shí)輸出是否一致
打印輸出
LoadAndInitializeTest[33804:2175226] +[MyObjectSuper load]
LoadAndInitializeTest[33804:2175226] +[MyObject load]
LoadAndInitializeTest[33804:2175226] -1.------華麗的風(fēng)格下------
LoadAndInitializeTest[33804:2175226] 類方法為:classFunction
LoadAndInitializeTest[33804:2175226] 類方法為:load
LoadAndInitializeTest[33804:2175226] -2.-------華麗的風(fēng)格下-----
LoadAndInitializeTest[33804:2175226] 實(shí)例方法為:objectFunction
LoadAndInitializeTest[33804:2175226] 實(shí)例方法為:.cxx_destruct
LoadAndInitializeTest[33804:2175226] 實(shí)例方法為:name
LoadAndInitializeTest[33804:2175226] 實(shí)例方法為:setName:
LoadAndInitializeTest[33804:2175226] 實(shí)例方法為:setAge:
LoadAndInitializeTest[33804:2175226] 實(shí)例方法為:age
LoadAndInitializeTest[33804:2175226] -3.-------華麗的風(fēng)格下-----
LoadAndInitializeTest[33804:2175226] 變量為:nIval
LoadAndInitializeTest[33804:2175226] 變量為:_name
LoadAndInitializeTest[33804:2175226] 變量為:_age
LoadAndInitializeTest[33804:2175226] -4.-------華麗的風(fēng)格下-----
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.-------華麗的風(fēng)格下-----
LoadAndInitializeTest[33804:2175226] 協(xié)議為:NSObject
LoadAndInitializeTest[33804:2175226] ------華麗的風(fēng)格下---------
LoadAndInitializeTest[33804:2175226] myObject.name=司曉剛,myObject.age=18
LoadAndInitializeTest[33804:2175226] +[MyObjectChild load]
前兩條和最后一條+load方法疫衩,我們都能看明白了硅蹦,直接忽視就好。
第一條華麗的分割線打印的是類方法闷煤,
我們成功的打印出classFunction童芹、load。
第二條華麗的分割線打印的是實(shí)例方法鲤拿,
objectFunction首先被打印出來假褪,
“.cxx_destruct”是什么,自己去研究吧近顷,
屬性name和屬性age的set生音、get方法被打印出來。
第三條華麗的分割線打印的是變量幕庐,
nIval是我們自己直接定義的久锥,
_name和_age是屬性name和age自動為我們生成的帶有下劃線的變量。
第四條華麗的分割線打印的是屬性
name和age就不用解釋了
superclass异剥、description瑟由、debugDescription也不解釋了,自己研究吧。
第五條華麗的分割線打印的是協(xié)議
NSObject 協(xié)議是我在頭文件中添加的歹苦。
到這兒青伤,最重要的一點(diǎn)開始了,
我實(shí)例化了一個(gè)MyObject對象殴瘦,并且給他賦值狠角,然后成功的打印出來了,
這就說明蚪腋,我們這個(gè)類以及完全可以正常使用了丰歌,難道這還不能說明類的+load方法是在類加載完成以后才調(diào)用的嗎?如果正在加載的話屉凯,我們能完整的得到類的這么多信息和使用類嗎立帖?
很顯然,我認(rèn)為我是對的悠砚,如有錯(cuò)誤之處晓勇,歡迎指正
讓我們重新回到Apple的那段文檔,開始下一個(gè)問題
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
Apple說灌旧,當(dāng)一個(gè)類或者分類被加入到runtime以后被調(diào)用绑咱,我以前一直認(rèn)為當(dāng)把一個(gè)類加入到runtime以后,立刻調(diào)用它的+load方法枢泰,然后再去加載它的兄弟類或者子類描融,也就是說,我在+load方法中宗苍,去獲取它子類的信息或者實(shí)例化子類稼稿,都不會成功的,因?yàn)轭愡€沒有加入到runtime中讳窟。
子類上面已經(jīng)說過了,它在父類的+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.------華麗的風(fēng)格下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.------華麗的風(fēng)格下Child------");
countMethod = 0;
methods = class_copyMethodList([myObjectChild class], &countMethod);
for (int i = 0; i < countMethod; i++) {
NSLog(@"Child實(shí)例方法為:%s", sel_getName(method_getName(methods[i])));
}
free(methods);
NSLog(@"-3.------華麗的風(fēng)格下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.------華麗的風(fēng)格下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.------華麗的風(fēng)格下Child-------");
countProtocol = 0;
protocols = class_copyProtocolList([myObjectChild class], &countProtocol);
for (int i = 0; i < countProtocol; i++) {
NSLog(@"Child協(xié)議為:%s", protocol_getName(protocols[i]));
}
NSLog(@"------華麗的風(fēng)格下Child------");
輸出打印
LoadAndInitializeTest[55040:2254257] ------以下是子類華麗的分割線------
LoadAndInitializeTest[55040:2254257] -1.------華麗的風(fēng)格下Child------
LoadAndInitializeTest[55040:2254257] myObjectChild.name=司曉剛,myObjectChild.age=18
LoadAndInitializeTest[55040:2254257] Child類方法為:classFunctionChild
LoadAndInitializeTest[55040:2254257] Child類方法為:load
LoadAndInitializeTest[55040:2254257] -2.------華麗的風(fēng)格下Child-------
LoadAndInitializeTest[55040:2254257] Child實(shí)例方法為:objectFunctionChild
LoadAndInitializeTest[55040:2254257] Child實(shí)例方法為:nameChild
LoadAndInitializeTest[55040:2254257] Child實(shí)例方法為:setNameChild:
LoadAndInitializeTest[55040:2254257] Child實(shí)例方法為:ageChild
LoadAndInitializeTest[55040:2254257] Child實(shí)例方法為:setAgeChild:
LoadAndInitializeTest[55040:2254257] Child實(shí)例方法為:.cxx_destruct
LoadAndInitializeTest[55040:2254257] -3.------華麗的風(fēng)格下Child-------
LoadAndInitializeTest[55040:2254257] Child變量為:nIvalChild
LoadAndInitializeTest[55040:2254257] Child變量為:_nameChild
LoadAndInitializeTest[55040:2254257] Child變量為:_ageChild
LoadAndInitializeTest[55040:2254257] -4.------華麗的風(fēng)格下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.------華麗的風(fēng)格下Child-------
LoadAndInitializeTest[55040:2254257] Child協(xié)議為:NSObject
這些輸出敞恋,我就不解釋了丽啡,輸出證明了,在父類的+load中已經(jīng)能獲取到子類的信息并且可以實(shí)例化子類了硬猫。
我證明的是把所有的類都加入到runtime以后补箍,然后開始調(diào)用+load方法,而不是Apple說的一個(gè)類啸蜜,對于這一點(diǎn)坑雅,挑戰(zhàn)性過大,請大神指正衬横。
四裹粤、+load方法是按照Compile Sources的排列順序加載的,但要遵循調(diào)用[ChildClass load]之前蜂林,必須先調(diào)用其[SuperClass load]方法遥诉。
- 在object文件夾中創(chuàng)建兩個(gè)MyObjectSuper的子類MyObject1和MyObject2拇泣,
在child文件夾中創(chuàng)建一個(gè)MyObject的子類MyObjectChild1
并且在三個(gè).m里實(shí)現(xiàn)以下代碼,然后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]
我們現(xiàn)在去查看以下Compile Sources矮锈,并且截圖如下
我們發(fā)現(xiàn)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 里的第一個(gè)類是MyObjectChild1债朵,
- 在調(diào)用[MyObjectChild1 load]之前會先調(diào)用其父類[MyObject load],
- 在調(diào)用[MyObject load]之前會調(diào)用其父類[MyObjectSuper load],
所以瀑凝,Compile Sources 里第一個(gè)類就打印出來前三個(gè)+load方法葱弟,
LoadAndInitializeTest[78440:2353132] +[MyObjectSuper load]
LoadAndInitializeTest[78440:2353132] +[MyObject load]
LoadAndInitializeTest[78440:2353132] +[MyObjectChild1 load]
- Compile Sources 里的第二個(gè)類是MyObjectChild,
- 在調(diào)用[MyObjectChild load]之前會先調(diào)用其父類[MyObject load]猜丹,因?yàn)楦割惖?load方法已經(jīng)被調(diào)用芝加,所以無需再調(diào)用。
- 在調(diào)用[MyObject load]之前會調(diào)用其父類[MyObjectSuper load]射窒,因?yàn)楦割惖?load方法已經(jīng)被調(diào)用藏杖,所以無需再調(diào)用。
所以脉顿,Compile Sources 里第二個(gè)類就打印出來第四個(gè)+load方法蝌麸,
LoadAndInitializeTest[78440:2353132] +[MyObjectChild load]
- [MyObject2 load]和[MyObject1 load]完全跟以上一致原理,請自行推理艾疟。
事實(shí)上来吩,Apple的文檔是這樣寫的
A class’s +load method is called after all of its superclasses’ +load methods.
一個(gè)類的+load方法在調(diào)用前,會先調(diào)用其父類的+load蔽莱。
我們實(shí)驗(yàn)得出的結(jié)論與Apple的文檔是一致的弟疆,如果Apple文檔再加上,類的+load方法按照Compile Sources里順序調(diào)用的盗冷,兩條規(guī)則合并起來就完美了怠苔。
五、在所有類的+load方法調(diào)用完以后再去調(diào)用[Category load]方法仪糖,[Category load]的調(diào)用順序完全按照Compile Sources排列順序柑司。
我們現(xiàn)在創(chuàng)建一系列的分類,如下锅劝,并分別實(shí)現(xiàn)其+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]
我們發(fā)現(xiàn),類的+load方法全部調(diào)用完以后才會調(diào)用[category load]方法故爵。
我們現(xiàn)在去修改category文件在Compile Sources里的順序玻粪,我們會很容易發(fā)現(xiàn),Compile Sources里的順序與我們輸出的順序,總是完全一致奶段。
Apple的文檔上是這么寫的
A category +load method is called after the class’s own +load method.
一個(gè)Category的+load方法在其所屬類的+load方法之后調(diào)用
蘋果的這段文檔饥瓷,我不能說他不對,但是我得到的結(jié)論是痹籍,[category load]的調(diào)用是在所有類的+load之后呢铆,而不是特定的類(the class)之后。
如有錯(cuò)誤蹲缠,歡迎指正棺克。
我們再做一個(gè)有趣的測試
我們修改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函數(shù)被調(diào)用的娜谊。
現(xiàn)在我對+load的使用進(jìn)行總結(jié):
一、+load 方法是在所有類被加入到runtime以后斤讥,main函數(shù)執(zhí)行之前被系統(tǒng)自動調(diào)用的纱皆。
二、系統(tǒng)自動為每一個(gè)類調(diào)用+load方法(如果有)芭商,無需手動調(diào)用派草,也無需手動調(diào)用[super load]。
三铛楣、+load方法會按照文件所在的Compile Sources順序加載近迁,在調(diào)用類的+load之前,會優(yōu)先調(diào)用其父類的+load方法簸州。
四鉴竭、在所有類的+load方法調(diào)用完以后再調(diào)用[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發(fā)送消息時(shí)祭埂,才會創(chuàng)建myLabel對象。
+initialize方法也是類似的原理兵钮,在類第一次接收消息時(shí)被調(diào)用。
事實(shí)上舌界,Apple的文檔是這么寫的掘譬,也就是說,他總在用戶調(diào)用之前調(diào)用呻拌。
Initializes the class before it receives its first message.
在這個(gè)類接收第一條消息之前調(diào)用葱轩。
關(guān)于+initialize方法,我總結(jié)如下
一、+ initialize 在類第一次接收到消息之前被系統(tǒng)自動調(diào)用靴拱,無需手動調(diào)用垃喊。
二、在調(diào)用子類的+ initialize 方法之前袜炕,會先調(diào)用父類的+ initialize 方法(如果有)本谜,所以也無需手動調(diào)用[super initialize]方法。
三偎窘、如果父類中有+ initialize方法乌助,而子類中沒有+ initialize方法,子類會自動繼承父類的+ initialize方法陌知,也就是說父類的+ initialize方法會調(diào)用兩次他托。
四、Category中+ initialize方法會覆蓋類中的+ initialize仆葡,同一個(gè)類有多個(gè)Category都實(shí)現(xiàn)了+initialize方法時(shí)赏参,Compile Sources 列表中最后一個(gè)Category 的+initialize方法會覆蓋其他的+ initialize方法。
一沿盅、+ initialize 在類第一次接收到消息之前被系統(tǒng)自動調(diào)用把篓,無需手動調(diào)用。
- 在MyObjectChild里添加如下代碼嗡呼,然后run
- (instancetype)init {
NSLog(@"init");
if (self = [super init]) {
}
return self;
}
+ (void)initialize {
NSLog(@"%s",__FUNCTION__);
}
然后無任何+ initialize輸出
我們現(xiàn)在在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 在類第一次接收到消息之前被系統(tǒng)自動調(diào)用南窗,無需手動調(diào)用揍很。
二、在調(diào)用子類的+ initialize 方法之前万伤,會先調(diào)用父類的+ initialize 方法(如果有)窒悔,所以也無需手動調(diào)用[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方法一樣振峻,在調(diào)用子類的方法時(shí)烫堤,會先調(diào)用父類的方法。
現(xiàn)在我們在MyObjectChild里加入以下代碼,然后run
[super initialize];
打印輸出
LoadAndInitializeTest[33679:2569542] +[MyObjectSuper initialize]
LoadAndInitializeTest[33679:2569542] +[MyObject initialize]
LoadAndInitializeTest[33679:2569542] +[MyObject initialize]
LoadAndInitializeTest[33679:2569542] +[MyObjectChild initialize]
這說明子類中無需手動調(diào)用[super initialize]方法。
三、如果父類實(shí)現(xiàn)了+ initialize方法,而子類沒有實(shí)現(xiàn)+ initialize,子類會自動繼承父類的+ initialize,也就是說,父類的+initialize方法,會被自動調(diào)用兩次,
現(xiàn)在我們注釋掉MyObjectChild观挎、MyObject 的+initialize,然后run
打印輸出
LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize]
LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize]
LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize]
我的天啊,[MyObjectSuper initialize]竟然被打印了三次,
因?yàn)镸yObject會繼承父類的+ initialize方法蕴轨,
而MyObjectChild也會繼承父類的+ initialize方法,
所以他們都繼承了MyObjectSuper的+ initialize方法斜筐,所以打印了三次屈梁。
在這我特別說明一點(diǎn)潭苞,+ initialize從名字上看遮婶,是初始化函數(shù),我們就會認(rèn)為只調(diào)用一次边败,而且其他很多博客里都明確說明+ initialize只調(diào)用一次排截,但事實(shí)上,他確實(shí)會自動調(diào)用多次,如果我這有錯(cuò)誤之處乔外,還希望能給指正。
因?yàn)? initialize方法會被自動繼承一罩,所以杨幼,+ initialize的出錯(cuò)率要比+load更大一些。
那+initialize方法里到底該怎么寫聂渊,我知道的通常有兩種辦法差购,
第一種
+ (void)initialize{
if (self == [MyClass class]) {
....
}
}
第二種
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
...
});
}
四、Category中+ initialize方法會覆蓋類中的+ initialize汉嗽,同一個(gè)類有多個(gè)Category都實(shí)現(xiàn)了+initialize方法時(shí)欲逃,Compile Sources 列表中最后一個(gè)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弓叛。
我們?nèi)ompile Sources中查看一下彰居,此時(shí)MyObjectChild category1肯定排在category0的后面,我們也可以隨意更改排列順序撰筷,Compile Sources中最后一個(gè)category肯定覆蓋其他所有的+ initialize方法陈惰。
我們也可以去修改其父類的category方法,發(fā)現(xiàn)父類也同樣遵守這樣的規(guī)則毕籽。
好吧抬闯,我這就寫完了井辆,+ initialize的總結(jié)與+ initialize開始總結(jié)的一模一樣,不重復(fù)總結(jié)了画髓。
這第一次寫技術(shù)文章掘剪,難免有不足之處,如果有錯(cuò)誤之處奈虾,還請指正。
如果你已經(jīng)能看到這里廉赔,說明你已經(jīng)足夠有耐心了肉微,
關(guān)于+load,我估計(jì)丟掉一個(gè)知識點(diǎn)蜡塌,你知道是什么嗎碉纳?
本文所有代碼已經(jīng)上傳至GitHub
寫本文時(shí),我重點(diǎn)參考了以下兩篇博客
類方法load和initialize的區(qū)別
iOS類方法load和initialize詳解