Objective - C Category(二)load 方法、initialize方法

(一)load方法

  • 類及其分類都有load方法,+load方法會在runtime加載類、分類時調(diào)用
  • 每個類、分類的+load,在程序運行過程中只調(diào)用一次
(1)調(diào)用順序
  1. 先調(diào)用類的+load
  • 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
  • 調(diào)用子類的+load之前會先調(diào)用父類的+load
  1. 再調(diào)用分類的+load
  • 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
(2)如何查看源碼證明調(diào)用順序?

源碼解讀順序:
(1) objc-os.mm文件

  • _objc_init
  • load_images

(2) objc-runtime-new.mm文件

  • load_images
  • load_images_nolock尋找\整理load方法
  • call_load_methods調(diào)用load方法

(3)objc-loadmethod.mm文件

  • call_load_methods方法 中 依次調(diào)用call_class_loadscall_category_loads
  • 查看這兩個方法 即直接依次獲取元類、分類的load方法并加載

重點函數(shù)講解:

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more 
        // 加載元類對象load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        //加載categoty的load方法
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

其中有call_class_loadscall_category_loads,下面只列舉call_class_loads

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    //這個classes為前面`schedule_class_load`方法通過"整理"出來的數(shù)組
    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方法
        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.
    //調(diào)用load方法
    if (classes) free(classes);
}
(3)load方法與自定義方法的區(qū)別

看到這里也大概有點疑惑,為什么分類的自定義方法會“掩蓋”類的原方法,而load不會?

  • +load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用
  • 自定義方法是經(jīng)過objc_msgSend函數(shù),進行一個消息查找的過程
(4)不同的類load方法調(diào)用順序

上面只搞清楚了類與分類之間的調(diào)用順序,那么不同的類(可能有繼承關(guān)系)調(diào)用順序又是什么樣的呢?
回到前面的方法load_images,在調(diào)用call_load_methods方法前,調(diào)用了一個load_images_nolock方法,里面有一個prepare_load_methods方法,我們看一下:

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

    runtimeLock.assertWriting();

    //通過編譯順序,獲取的類數(shù)組
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //定制規(guī)劃一些
        schedule_class_load(remapClass(classlist[i]));
    }

    //分類調(diào)用順序 類的load調(diào)用完畢再調(diào)用分類,先編譯先調(diào)用
    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);
    }
}

我們再看其中的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
    //遞歸調(diào)用(優(yōu)先尋找其父類的load方法)
    schedule_class_load(cls->superclass);
    
    //將該類添加到loadable_list中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

這個loadable_list是在后面call_class_loads調(diào)用時用到的,即可說明:

  • 不同的類之間的load方法調(diào)用,按編譯順序先后
  • 即使子類最初的編譯順序在前,也會優(yōu)先調(diào)用父類的load方法
    (默認按照編譯順序,但是要將父類優(yōu)先load)
  • 分類是按照編譯順序依次調(diào)用

現(xiàn)在我們也能回答前面的面試題了:

4. Category中有l(wèi)oad方法嗎?load方法是什么時候調(diào)用的?load 方法能繼承嗎?

答:有l(wèi)oad方法;load方法是在runtime加載類、分類的時候調(diào)用一次;load方法可以繼承的,但是load方法一般是系統(tǒng)在調(diào)用,一般不會主動調(diào)用

(二)initialize方法

  • +initialize方法會在類第一次接收到消息時調(diào)用(第一次alloc)
(1)調(diào)用順序
  1. 先調(diào)用父類的+initialize,再調(diào)用子類的+initialize
  • 先初始化父類,再初始化子類,每個類只會初始化1次
(1)如何查看源碼證明調(diào)用順序?

源碼解讀順序:
(1) objc-msg-arm64.s文件

  • objc_msgSend

(2) objc-runtime-new.mm文件

  • class_getInstanceMethod
  • lookUpImpOrNil
  • lookUpImpOrForward
  • _class_initialize
  • callInitialize
  • objc_msgSend(cls, SEL_initialize)
(2)+initialize和+load的區(qū)別
  • +initialize是通過objc_msgSend進行調(diào)用的,所以有以下特點
  • 如果子類沒有實現(xiàn)+initialize,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次,但不代表被初始化多次,只是objc_msgSend調(diào)用了父類的initialize方法)
  • 如果分類實現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用

類方法調(diào)用:(還是會調(diào)用class_getInstanceMethod)

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);//將自身傳進去
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if (!cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

    //判斷是否需要初始化,且還沒有被初始化過
    if (initialize  &&  !cls->isInitialized()) {
        //(第一次的時候才會執(zhí)行)
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // 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
    }

    ....//代碼省略

    return imp;
}

我們再看_class_initialize方法:

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

    Class supercls;
    BOOL reallyInitialize = NO;

    //重點: initialize之前需要保證父類已initialized 
    // 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);
        
        // 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: calling +[%s initialize]",
                         cls->nameForLogging());
        }
        
        //重點:通過objc_msgSend方法調(diào)用initialize方法
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         cls->nameForLogging());
        }        
        
        // Done initializing. 
        // If the superclass is also done initializing, then update 
        //   the info bits and notify waiting threads.
        // If not, update them later. (This can happen if this +initialize 
        //   was itself triggered from inside a superclass +initialize.)
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
        return;
    }

   ...//代碼省略
}

因此,通過該方法可以看出, 此處是會先調(diào)用父類的initalize,再調(diào)用子類的initalize

load和initialize方法的區(qū)別是什么?

  1. 調(diào)用方式
  • load根據(jù)函數(shù)地址直接調(diào)用
  • initialize是通過objc_msgSend調(diào)用
    2.調(diào)用時刻
  • load是runtime加載類、分類的時候調(diào)用
  • initialize是類第一次接受到消息時調(diào)用,且每個類只會被初始化一次(父類的initialize可能會調(diào)用多次,子類可能并未實現(xiàn)initialize方法)
    3.調(diào)用順序
  • load方法,①先調(diào)用類的load,先編譯的類優(yōu)先調(diào)用load,調(diào)用子類的load之前會調(diào)用父類的load ②其次調(diào)用分類的load,先編譯優(yōu)先調(diào)用
  • initialize 先初始化父類,再初始化子類(子類可能因為未實現(xiàn)initialize,通過objc_msgSend調(diào)用父類方法)

ps: 上一期的遺留,同時思考load與initalize方法調(diào)用順序,即原理

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

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

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