+load方法與+initialize方法的區(qū)別

兩個(gè)方法的區(qū)別

1.兩個(gè)方法的調(diào)用方式

load是拿到函數(shù)地址直接進(jìn)行調(diào)用
initialize是通過objc_msgSend()進(jìn)行調(diào)用的

2.兩個(gè)方法的調(diào)用時(shí)機(jī)

load是runtime加載類瘪撇,分類的時(shí)候調(diào)用的(只調(diào)用一次)
initialize是類第一次接收到消息時(shí)調(diào)用的诗赌,而且每個(gè)類只能被初始化一次(父類initialize方法可能被調(diào)用多次)

3.兩個(gè)方法的調(diào)用順序

  • load

    1. 先調(diào)用類的load方法
      先編譯的類優(yōu)先調(diào)用
      調(diào)用子類的load的之前妆兑,會(huì)先調(diào)用父類的load方法
    2. 再調(diào)用分類的load方法
      先編譯的分類優(yōu)先先調(diào)用(只看編譯順序,不區(qū)分是父類的分類還是子類的分類)
  • initialize

    先初始化父類
    再初始化子類(可能最終調(diào)用的是父類的initialize方法)

load源碼分析步驟:

objc4源碼解讀過程:objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
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++) {
        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);
}

從上面看到+load方法是根據(jù)方法地址直接調(diào)用乳绕,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用,loadable_classes里存放著很多下面這種結(jié)構(gòu)體:
cls也就是哪個(gè)類疯趟,method就是load方法
struct loadable_class {
Class cls;
IMP method;
};

遍歷loadable_classes數(shù)組,拿到load方法地址直接調(diào)用,那么也就是數(shù)組的順序就是調(diào)用load方法的順序叭首,那就接著看看這個(gè)數(shù)組是怎么添加的,看prepare_load_methods這個(gè)函數(shù)

 void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

看到這個(gè)函數(shù)schedule_class_load,再接著深入看

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

可以看到schedule_class_load(cls->superclass);這行代碼會(huì)遞歸將父類填到到這個(gè)列表踪栋,然后再添加當(dāng)前類焙格,
if (cls->data()->flags & RW_LOADED) return;
這行代碼是判斷是否添加過,如果添加過直接return夷都,保證每個(gè)類只調(diào)用一次load方法
cls->setInfo(RW_LOADED); 這行代碼是將類添加到數(shù)組里時(shí)設(shè)置的標(biāo)識位用來判斷是否添加過

而分類就是正常遍歷直接添加眷唉,所以就是按照編譯順序調(diào)用的,先編譯先調(diào)用
綜上大家應(yīng)該了解了+load方法調(diào)用的底層結(jié)構(gòu)了囤官,如果手動(dòng)調(diào)用[self load]方法冬阳,調(diào)用的還是objc_msgSend(),則按照正常方法調(diào)用步驟,找isa党饮,superclass肝陪,一步步尋找方法進(jìn)行調(diào)用,load方法可能會(huì)被分類覆蓋刑顺,但是一般不需要手動(dòng)調(diào)用见坑,

+initialize源碼分析步驟:

objc4源碼解讀過程
objc-msg-arm64.s
objc_msgSend
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();
    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.
    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.
    runtimeLock.read();
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();
        realizeClass(cls);
        runtimeLock.unlockWrite();
        runtimeLock.read();
    }
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
 retry:    
    runtimeLock.assertReading();
    // Try this class's cache.
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }
    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}
  • 看上面的源碼可以看到下面片段代碼,會(huì)先判斷當(dāng)前類是否初始化過捏检,initialize參數(shù)系統(tǒng)傳的是YES,所以當(dāng)前類沒初始化過就調(diào)用_class_initialize()
 if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

_class_initialize源碼如下

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         pthread_self(), cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        //
        // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
        // bootstrapping problem of this versus CF's call to
        // objc_exception_set_functions().
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             pthread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            // We're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

下面代碼是一部分可以看到荞驴,初始化當(dāng)前類時(shí)會(huì)先判斷父類是否為真,父類是否初始化贯城,如果未初始化去初始化父類熊楼,遞歸調(diào)用。

supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
  • 父類初始化完成在初始化當(dāng)前類調(diào)用callInitialize能犯,如下面代碼所示,初始化完成調(diào)用lockAndFinishInitializing

callInitialize源碼

  • 如下鲫骗,可以看到是掉用的objc_msgSend()
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             pthread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }

lockAndFinishInitializing源碼,將類標(biāo)識為已初始化將flags= RW_INITIALIZED,然后判斷是否初始化代碼:

bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}

static void lockAndFinishInitializing(Class cls, Class supercls)
{
    monitor_locker_t lock(classInitLock);
    if (!supercls  ||  supercls->isInitialized()) {
        _finishInitializing(cls, supercls);
    } else {
        _finishInitializingAfter(cls, supercls);
    }
}

如果你看明白了上面的原理踩晶,看看下面這種情況會(huì)打印什么执泰,如果你還能猜到打印什么,那證明你真的看懂了原理渡蜻,

@interface LSPerson : NSObject
@end
@implementation LSPerson
+ (void)initialize{
    NSLog(@"LSPerson +initialize");
}
@end



@interface LSStudent : LSPerson
@end
@implementation LSStudent
+ (void)initialize{
    NSLog(@"LSStudent +initialize");
}
@end



@interface LSTeacher : LSPerson
@end
@implementation LSTeacher
+ (void)initialize{
    NSLog(@"LSTeacher +initialize");
}
@end
  • 1.第一種情況 正常運(yùn)行术吝,結(jié)果在最下面
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [LSStudent alloc];
        [LSTeacher alloc];
    }
    return 0;
}
  • 2.第二種情況 將 LSStudent里的load方法注釋掉,然后運(yùn)行
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [LSStudent alloc];
        [LSTeacher alloc];
    }
    return 0;
}
  • 3.第三種情況 將 LSStudent里的load方法注釋掉茸苇,將 LSTeacher里的load方法也注釋掉排苍,然后運(yùn)行
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [LSStudent alloc];
        [LSTeacher alloc];
    }
    return 0;
}
  • 4.第四種情況 將 LSStudent里的load方法注釋掉,將 LSTeacher里的load方法也注釋掉学密,然后運(yùn)行
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [LSStudent initialize];
        [LSTeacher initialize];
    }
    return 0;
}
  • 第一種情況打印結(jié)果:都不注釋

LSPerson +initialize
LSStudent +initialize
LSTeacher +initialize

  • 第二種情況打印結(jié)果:注釋LSStudent的initialize方法

LSPerson +initialize
LSPerson +initialize
LSTeacher +initialize

  • 第三種情況打印結(jié)果:注釋LSStudent的initialize方法淘衙,注釋LSTeacher的initialize方法

LSPerson +initialize
LSPerson +initialize
LSPerson +initialize

  • 第四種情況打印結(jié)果:

LSPerson +initialize
LSPerson +initialize
LSPerson +initialize
LSPerson +initialize
LSPerson +initialize

以下是調(diào)用
[LSStudent alloc];
[LSTeacher alloc];
的偽代碼,如果是objc_msgSend([LSStudent class],@selector(initialize)),而student類沒有就會(huì)從父類找腻暮,所以掉了父類的initialize方法彤守,并不是初始化了多次毯侦,teacher也是類似,而第四種情況是具垫,類第一次收到消息觸發(fā)initialize方法侈离,然后所有邏輯都走完之后再去調(diào)用你真正想調(diào)用的initialize方法,所以比第三種情況多2打印了遍

        BOOL sutdentInitialized = NO;
        BOOL personInitialized = NO;
        BOOL teacherInitialized = NO;
        
        if (!sutdentInitialized) {
            if (!personInitialized) {
                objc_msgSend(LSStudent.superclass, @selector(initialize));
                personInitialized = YES;
            }
            objc_msgSend([LSStudent class], @selector(initialize));
            sutdentInitialized = YES;
        }
        
        
        if (!teacherInitialized) {
            if (!personInitialized) {
                objc_msgSend(LSTeacher.superclass, @selector(initialize));
                personInitialized = YES;
            }

            objc_msgSend([LSTeacher class], @selector(initialize));
            teacherInitialized = YES;
        }

最后附上兩個(gè)方法的分析結(jié)論和步驟圖

load方法

屏幕快照 2018-11-21 下午2.19.21.png

initialize方法
屏幕快照 2018-11-21 下午2.19.27.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末做修,一起剝皮案震驚了整個(gè)濱河市霍狰,隨后出現(xiàn)的幾起案子抡草,更是在濱河造成了極大的恐慌饰及,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件康震,死亡現(xiàn)場離奇詭異燎含,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)腿短,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門屏箍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橘忱,你說我怎么就攤上這事赴魁。” “怎么了钝诚?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵颖御,是天一觀的道長。 經(jīng)常有香客問我凝颇,道長潘拱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任拧略,我火速辦了婚禮芦岂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘垫蛆。我一直安慰自己禽最,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布袱饭。 她就那樣靜靜地躺著弛随,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宁赤。 梳的紋絲不亂的頭發(fā)上舀透,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音决左,去河邊找鬼愕够。 笑死走贪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惑芭。 我是一名探鬼主播坠狡,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遂跟!你這毒婦竟也來了逃沿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤幻锁,失蹤者是張志新(化名)和其女友劉穎凯亮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哄尔,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡假消,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了岭接。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片富拗。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸣戴,靈堂內(nèi)的尸體忽然破棺而出啃沪,到底是詐尸還是另有隱情,我是刑警寧澤窄锅,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布创千,位于F島的核電站,受9級特大地震影響酬滤,放射性物質(zhì)發(fā)生泄漏签餐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一盯串、第九天 我趴在偏房一處隱蔽的房頂上張望氯檐。 院中可真熱鬧,春花似錦体捏、人聲如沸冠摄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽河泳。三九已至,卻和暖如春年栓,著一層夾襖步出監(jiān)牢的瞬間拆挥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工某抓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纸兔,地道東北人惰瓜。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像汉矿,于是被迫代替她去往敵國和親崎坊。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348