深入詳解 iOS的 +load和+initialize

在這之前,我從沒有想過,+load和+initialize能扯出這么多東西來,但今天確實扯出這么多,如有錯誤之處,歡迎指正哈~~~

  • +load 方法是系統(tǒng)自動調(diào)用的,無需手動調(diào)用,系統(tǒng)自動為每一個類調(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中的輸出時間全部去掉了,

學習的開始,首先我們新建工程一個LoadAndInitializeTest項目

一、+load 方法是系統(tǒng)自動調(diào)用的,無需手動調(diào)用,系統(tǒng)自動為每一個類調(diào)用+load方法(如果有),所以也無需手動調(diào)用[super load]方法。

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

打印輸出

LoadAndInitializeTest[27822:1740708] +[MyObject load]

我們并沒有手動調(diào)用MyObject的任何方法,但是+load方法確實調(diào)用了,所以+load 方法是系統(tǒng)自動調(diào)用的,無需手動調(diào)用。

  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方法也是自動加載的,無需手動調(diào)用。

  1. 在[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
    });
}
  1. 我們再多做一個測試,如果某個程序員在[ChildClass load]中,手賤寫一個[super load],而[SuperClass load]的職責是利用黑魔法進行方法交換,[SuperClass load]就會調(diào)用兩次,方法交換了兩次,就等于沒有交換了,如果不懂+load方法的使用,像這個的bug,我們卻很難發(fā)現(xiàn)。

在父類(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

我們在其他地方創(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]

分析結果:[MyObjectSuper load]被調(diào)用了兩次,方法被交換了兩次,等于沒有交換,所以對象銷毀時,調(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方法中,我們必須做判斷,因為總有一個程序員會繼承你的類,然后在load方法中調(diào)用[super load]。

  1. 現(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]了

  1. 我在閱讀一些知名的第三庫時,如AFNetworking、以及小碼哥的MJRefresh時,確實發(fā)現(xiàn)一些不嚴謹?shù)淖龇ǎm然并不是嚴重的bug,但是總歸是隱患,代碼如下
AFNetworking 代碼,只貼一小部分了
@implementation _AFURLSessionTaskSwizzling
+ (void)load {
    if (NSClassFromString(@"NSURLSessionTask")) {
        我創(chuàng)建了一個_AFURLSessionTaskSwizzling的子類,
        在子類中重新+load方法,然后[super load];
        發(fā)現(xiàn)程序確實能調(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);
}

在滴滴出行客戶端里是這樣寫的,可見我大滴滴早就注意到這個問題了

@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文件夾下創(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 里的順序,結果依然如此,證明了+load方法的調(diào)用順序是從父類順序到子類的。

三、+load 方法是在所有類被加入到runtime以后調(diào)用的。

在分享這個問題之前,我們先來看一小段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中時(就是被引用的時候)。實現(xiàn)該方法,可以在加載時做一些類特有的操作。

而我是這么翻譯的

當類或者類別加入到Runtime中以后,實現(xiàn)該方法可以在加載時做一些類特有的操作。

以上兩段翻譯根本不同點是“時”和“以后”,我認為“時”,是正在進行時,是正在添加。而“以后”是Add操作成功以后的事,是一種完成時態(tài)。
現(xiàn)在我們就來測一下,到底是怎么回事!

修改MyObject.h如下
遵守了一個NSObject的協(xié)議,添加了兩個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(@"協(xié)議為:%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

現(xiàn)在你可以多想一想,你心中認為的和真實輸出是否一致
打印輸出

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] 協(xié)議為: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也不解釋了,自己研究吧。

第五條華麗的分割線打印的是協(xié)議
NSObject 協(xié)議是我在頭文件中添加的。

到這兒,最重要的一點開始了,
我實例化了一個MyObject對象,并且給他賦值,然后成功的打印出來了,
這就說明,我們這個類以及完全可以正常使用了,難道這還不能說明類的+load方法是在類加載完成以后才調(diào)用的嗎?如果正在加載的話,我們能完整的得到類的這么多信息和使用類嗎?

很顯然,我認為我是對的,如有錯誤之處,歡迎指正

讓我們重新回到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以后被調(diào)用,我以前一直認為當把一個類加入到runtime以后,立刻調(diào)用它的+load方法,然后再去加載它的兄弟類或者子類,也就是說,我在+load方法中,去獲取它子類的信息或者實例化子類,都不會成功的,因為類還沒有加入到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.------華麗的風格下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協(xié)議為:%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協(xié)議為:NSObject

這些輸出,我就不解釋了,輸出證明了,在父類的+load中已經(jīng)能獲取到子類的信息并且可以實例化子類了。

我證明的是把所有的類都加入到runtime以后,然后開始調(diào)用+load方法,而不是Apple說的一個類,對于這一點,挑戰(zhàn)性過大,請大神指正。

四、+load方法是按照Compile Sources的排列順序加載的,但要遵循調(diào)用[ChildClass load]之前,必須先調(diào)用其[SuperClass load]方法。

  1. 在object文件夾中創(chuàng)建兩個MyObjectSuper的子類MyObject1和MyObject2,
    在child文件夾中創(chuàng)建一個MyObject的子類MyObjectChild1
    并且在三個.m里實現(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,并且截圖如下


屏幕快照 2018-11-24 下午11.18.26.png

我們發(fā)現(xiàn)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,
  • 在調(diào)用[MyObjectChild1 load]之前會先調(diào)用其父類[MyObject load],
  • 在調(diào)用[MyObject load]之前會調(diào)用其父類[MyObjectSuper load],
    所以,Compile Sources 里第一個類就打印出來前三個+load方法,
LoadAndInitializeTest[78440:2353132] +[MyObjectSuper load]
LoadAndInitializeTest[78440:2353132] +[MyObject load]
LoadAndInitializeTest[78440:2353132] +[MyObjectChild1 load]
  • Compile Sources 里的第二個類是MyObjectChild,
  • 在調(diào)用[MyObjectChild load]之前會先調(diào)用其父類[MyObject load],因為父類的+load方法已經(jīng)被調(diào)用,所以無需再調(diào)用。
  • 在調(diào)用[MyObject load]之前會調(diào)用其父類[MyObjectSuper load],因為父類的+load方法已經(jīng)被調(diào)用,所以無需再調(diào)用。
    所以,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方法在調(diào)用前,會先調(diào)用其父類的+load。

我們實驗得出的結論與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)建一系列的分類,如下,并分別實現(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.
一個Category的+load方法在其所屬類的+load方法之后調(diào)用

蘋果的這段文檔,我不能說他不對,但是我得到的結論是,[category load]的調(diào)用是在所有類的+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函數(shù)被調(diào)用的。

現(xiàn)在我對+load的使用進行總結:

一、+load 方法是在所有類被加入到runtime以后,main函數(shù)執(zhí)行之前被系統(tǒng)自動調(diào)用的。

二、系統(tǒng)自動為每一個類調(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ā)送消息時,才會創(chuàng)建myLabel對象。
+initialize方法也是類似的原理,在類第一次接收消息時被調(diào)用。

事實上,Apple的文檔是這么寫的,也就是說,他總在用戶調(diào)用之前調(diào)用。

Initializes the class before it receives its first message.
在這個類接收第一條消息之前調(diào)用。

關于+initialize方法,我總結如下

一、+ 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,同一個類有多個Category都實現(xiàn)了+initialize方法時,Compile Sources 列表中最后一個Category 的+initialize方法會覆蓋其他的+ initialize方法。

一、+ initialize 在類第一次接收到消息之前被系統(tǒng)自動調(diào)用,無需手動調(diào)用。

  1. 在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)用子類的方法時,會先調(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]方法。

三、如果父類實現(xiàn)了+ initialize方法,而子類沒有實現(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]竟然被打印了三次,
因為MyObject會繼承父類的+ initialize方法,
而MyObjectChild也會繼承父類的+ initialize方法,
所以他們都繼承了MyObjectSuper的+ initialize方法,所以打印了三次。

在這我特別說明一點,+ initialize從名字上看,是初始化函數(shù),我們就會認為只調(diào)用一次,而且其他很多博客里都明確說明+ initialize只調(diào)用一次,但事實上,他確實會自動調(diào)用多次,如果我這有錯誤之處,還希望能給指正。

因為+ initialize方法會被自動繼承,所以,+ initialize的出錯率要比+load更大一些。

那+initialize方法里到底該怎么寫,我知道的通常有兩種辦法,
第一種

+ (void)initialize{
    if (self == [MyClass class]) {
          ....
    }
}

第二種

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            ...
    });
}

四、Category中+ initialize方法會覆蓋類中的+ initialize,同一個類有多個Category都實現(xiàn)了+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。

我們?nèi)ompile Sources中查看一下,此時MyObjectChild category1肯定排在category0的后面,我們也可以隨意更改排列順序,Compile Sources中最后一個category肯定覆蓋其他所有的+ initialize方法。

我們也可以去修改其父類的category方法,發(fā)現(xiàn)父類也同樣遵守這樣的規(guī)則。

好吧,我這就寫完了,+ initialize的總結與+ initialize開始總結的一模一樣,不重復總結了。

這第一次寫技術文章,難免有不足之處,如果有錯誤之處,還請指正。

如果你已經(jīng)能看到這里,說明你已經(jīng)足夠有耐心了,
關于+load,我估計丟掉一個知識點,你知道是什么嗎?

本文所有代碼已經(jīng)上傳至GitHub

寫本文時,我重點參考了以下兩篇博客
類方法load和initialize的區(qū)別
iOS類方法load和initialize詳解

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容