iOS進階回顧四「load & initialize」

load方法的本質(zhì)是什么椿猎?initialize呢?兩個有什么區(qū)別?

  • 最好的研究方法就是實踐:新建兩個類實現(xiàn)load方法

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson : NSObject
@end
NS_ASSUME_NONNULL_END

#import "SFPerson.h"
@implementation SFPerson
+ (void)load{
    NSLog(@"SFPerson  +load");
}
@end

#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Helper)
@end
NS_ASSUME_NONNULL_END

#import "SFPerson+Helper.h"
@implementation SFPerson (Helper)
+ (void)load{
    NSLog(@"SFPerson (Helper) + load");
}
@end

#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Addtion)
@end
NS_ASSUME_NONNULL_END

#import "SFPerson+Addtion.h"

@implementation SFPerson (Addtion)

+ (void)load{
    NSLog(@"SFPerson (Addtion) + load");
}

@end

2019-08-27 14:16:08.683691+0800 load[33088:238375] SFPerson +load
2019-08-27 14:16:08.684128+0800 load[33088:238375] SFPerson (Helper) + load
2019-08-27 14:38:15.283433+0800 load[33282:256018] SFPerson (Addtion) + load

直接運行后出現(xiàn)上面的打印輸出暖庄,我們沒有導(dǎo)入頭文件,也沒有進行創(chuàng)建對象調(diào)用楼肪,系統(tǒng)直接調(diào)用load方法由此可見:
分類中也存在load方法培廓,load方法是在程序啟動時,加載類春叫、分類的時候就會調(diào)用肩钠。在調(diào)用分類的load方法前會優(yōu)先調(diào)用本類的load方法。分類中的load方法誰先編譯誰先調(diào)用
我們也可以通過源碼驗證如下:

22E093B692B25434CAC728BFA387A090.png

可以看到類的load方法調(diào)用一次就不會調(diào)用了loadable_classes_user>0這樣循環(huán)就不會再次執(zhí)行了
繼續(xù)查看call_class_loads()類方法

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
    直接通過數(shù)組的下標得到我們需要的方法
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

call_category_loads()分類方法的實現(xiàn)

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",
                         loadable_categories_used);
        }
    }

    return new_categories_added;
}
  • 我們看到無論是分類還是類都是通過找到下標直接調(diào)用的沒有通過msgSend消息轉(zhuǎn)發(fā)暂殖,所以load方法的本質(zhì)調(diào)用為:load根據(jù)函數(shù)地址直接調(diào)用价匠,在runtime加載類分類的時候調(diào)用,只會調(diào)用一次呛每,先調(diào)用類的load方法再去調(diào)用分類的load踩窖,先編譯的類,先調(diào)用晨横,調(diào)用子類的load之前洋腮,先調(diào)用父類的load方法

  • 我們再添加一個SFStudent類繼承SFPerson分別實現(xiàn)一個類方法run方法

#import "SFPerson.h"

@implementation SFPerson

+ (void)load{
    NSLog(@"SFPerson  +load");
}
+(void)run{
    NSLog(@"SFPerson  +run");

}
@end

利用[SFStudent run];進行調(diào)用輸入如下:

2019-08-27 14:45:34.859386+0800 load[33449:263656] SFPerson +load
2019-08-27 14:45:34.859677+0800 load[33449:263656] SFPerson (Helper) + load
2019-08-27 14:45:34.859693+0800 load[33449:263656] SFPerson (Addtion) + load
2019-08-27 14:45:34.859769+0800 load[33449:263656] SFPerson (Addtion) +run

由此可知:分類中重寫類方法時箫柳,分類的類方法會優(yōu)先調(diào)用。我們利用SFStudent調(diào)用run方法時啥供,輸出為SFPerson (Addtion) +run優(yōu)先調(diào)用的原因

下面繼續(xù)initialize分析:

  • 我們在SFPerson中添加+(void)initialize方法
#import "SFPerson.h"

@implementation SFPerson

+(void)initialize{
    NSLog(@" SFPerson initialize ");
}
@end

main.m中調(diào)用如下

#import <Foundation/Foundation.h>
#import "SFStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [SFStudent run];
    }
    return 0;
}

2019-08-27 15:39:29.802479+0800 load[39941:335527] SFPerson initialize
2019-08-27 15:39:29.802491+0800 load[39941:335527] SFPerson initialize

  • 我們看到調(diào)用了兩次initialize方法悯恍,為什么呢?
#import <Foundation/Foundation.h>
#import "SFStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [SFStudent run];
        [SFStudent run];
        [SFStudent run];

    }
    return 0;
}

2019-08-27 16:20:53.860266+0800 load[40134:360148] SFPerson initialize
2019-08-27 16:20:53.860282+0800 load[40134:360148] SFPerson initialize

  • 多次調(diào)用的輸入結(jié)果和上面的一樣
    initialize方法會在類第一次接收消息的時候調(diào)用 調(diào)用順序:先調(diào)用父類的+initialize伙狐,在調(diào)用子類的+initialize無論多少次調(diào)用都執(zhí)行一次涮毫,是類第一次接收到消息的時候調(diào)用,每一個類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)
    查看源碼:
    9FF98C1983E5B2CA36457C94BE87F727.png

    查看源碼可以看到initialize方法是通過消息轉(zhuǎn)發(fā)機制實現(xiàn)的
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

調(diào)用時間:

  • load方法是可以繼承的贷屎,但是一般情況下窒百,都是不會主動調(diào)用load方法,都是讓系統(tǒng)自動調(diào)用
  • + initialize方法會在類第一次接收消息的時候調(diào)用
    調(diào)用順序:
  • 先調(diào)用父類的+initialize豫尽,在調(diào)用子類+initialize父類的初始化方法可能調(diào)用多次, 先初始化父類再初始化子類篙梢,而且每個類只會初始化一次, 如果分類實現(xiàn)了+initialize,就覆蓋類本身的+initialize方法
  • 先調(diào)用類的load方法再去調(diào)用分類的load美旧,先編譯的類渤滞,先調(diào)用,調(diào)用子類的load之前榴嗅,先調(diào)用父類的load
    調(diào)用方式的:
  • +initialize是通過objc_msgSend進行調(diào)用的
  • load根據(jù)函數(shù)地址直接調(diào)用妄呕,在runtime加載類分類的時候調(diào)用,只會調(diào)用一次
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗽测,一起剝皮案震驚了整個濱河市绪励,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唠粥,老刑警劉巖疏魏,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晤愧,居然都是意外死亡大莫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門官份,熙熙樓的掌柜王于貴愁眉苦臉地迎上來只厘,“玉大人,你說我怎么就攤上這事舅巷「嵛叮” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵钠右,是天一觀的道長赋元。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么们陆? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任寒瓦,我火速辦了婚禮情屹,結(jié)果婚禮上坪仇,老公的妹妹穿的比我還像新娘。我一直安慰自己垃你,他們只是感情好椅文,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惜颇,像睡著了一般皆刺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凌摄,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天羡蛾,我揣著相機與錄音,去河邊找鬼锨亏。 笑死痴怨,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的器予。 我是一名探鬼主播浪藻,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乾翔!你這毒婦竟也來了爱葵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤反浓,失蹤者是張志新(化名)和其女友劉穎萌丈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷则,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡浓瞪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了巧婶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乾颁。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖艺栈,靈堂內(nèi)的尸體忽然破棺而出英岭,到底是詐尸還是另有隱情,我是刑警寧澤湿右,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布诅妹,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吭狡。R本人自食惡果不足惜尖殃,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望划煮。 院中可真熱鬧送丰,春花似錦、人聲如沸弛秋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟹略。三九已至登失,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挖炬,已是汗流浹背揽浙。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留意敛,地道東北人馅巷。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像空闲,于是被迫代替她去往敵國和親令杈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

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