深入詳解 iOS的 +load和+initialize

在這之前,我從沒有想過,+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]方法。

  1. 在Xcode中新建文件夾(object),然后新建一個NSObject的子類MyObject。
  2. 在MyObject的.m文件中加入以下代碼,然后run
+(void)load {
    NSLog(@"%s",__FUNCTION__);
}

打印輸出

LoadAndInitializeTest[27822:1740708] +[MyObject load]

我們并沒有手動調用MyObject的任何方法,但是+load方法確實調用了,所以+load 方法是系統自動調用的,無需手動調用。

  1. 在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方法也是自動加載的,無需手動調用。

  1. 在[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
    });
}
  1. 我們再多做一個測試,如果某個程序員在[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]。

  1. 現在我們在父類中的修改代碼如下
+ (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]了

  1. 我在閱讀一些知名的第三庫時,如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]->順序加載的。

  1. 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]方法。

  1. 在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]
  1. [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 在類第一次接收到消息之前被系統自動調用,無需手動調用。

  1. 在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方法。

  1. 在@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。

  1. 在@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詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容