深入詳解 iOS的 +load和+initialize

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

  1. 在Xcode中新建文件夾(object)锥腻,然后新建一個(gè)NSObject的子類MyObject。
  2. 在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)用幸斥。

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

  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. 我們再多做一個(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]陨晶。

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

  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 里的順序鳍鸵,結(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]方法遥诉。

  1. 在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矮锈,并且截圖如下


屏幕快照 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 里的第一個(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]
  1. [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)用。

  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)用子類的方法時(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方法。

  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中查看一下彰居,此時(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詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馏艾,一起剝皮案震驚了整個(gè)濱河市劳曹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琅摩,老刑警劉巖铁孵,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異房资,居然都是意外死亡蜕劝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門轰异,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岖沛,“玉大人,你說我怎么就攤上這事搭独∮は鳎” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵牙肝,是天一觀的道長唉俗。 經(jīng)常有香客問我,道長惊奇,這世上最難降的妖魔是什么互躬? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任确封,我火速辦了婚禮新博,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恩溅。我一直安慰自己乓序,他們只是感情好寺酪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布坎背。 她就那樣靜靜地躺著,像睡著了一般寄雀。 火紅的嫁衣襯著肌膚如雪得滤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天盒犹,我揣著相機(jī)與錄音懂更,去河邊找鬼。 笑死急膀,一個(gè)胖子當(dāng)著我的面吹牛沮协,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卓嫂,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼慷暂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晨雳?” 一聲冷哼從身側(cè)響起行瑞,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎餐禁,沒想到半個(gè)月后血久,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坠宴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年洋魂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喜鼓。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡副砍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庄岖,到底是詐尸還是另有隱情豁翎,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布隅忿,位于F島的核電站心剥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏背桐。R本人自食惡果不足惜优烧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望链峭。 院中可真熱鬧畦娄,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至驳癌,卻和暖如春滑燃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颓鲜。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工表窘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灾杰。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓蚊丐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艳吠。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • iOS開發(fā)中總能看到+load和+initialize的身影,網(wǎng)上對于這兩個(gè)方法有很多解釋,官方也有說明,但有些細(xì)...
    朱曉輝閱讀 27,470評論 19 139
  • find /etc -name "*.conf" -> 在etc目錄下面查找conf文件grep -> 查找字...
    雨雨雨90閱讀 503評論 0 0
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉孽椰,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,719評論 0 9
  • Java事務(wù)之一——Java事務(wù)的基本問題 是一個(gè)系列文章昭娩,里面很多鏈接訪問到相關(guān)的知識,適合仔細(xì)閱讀入門 關(guān)于加...
    Albert陳凱閱讀 806評論 0 0
  • 這首《從此刻起:我要》強(qiáng)烈推薦給各位親愛的家長朋友黍匾。教育這件事栏渺,只要開始了,永遠(yuǎn)都不會晚锐涯! 1)從此刻起:我要多鼓...
    悅心教育楊小媚閱讀 594評論 2 0