刨根問底+load方法

??首先我們都知道在iOS應(yīng)用啟動的時候會調(diào)用所有類和其分類的+load方法。子類的load方法會在父類方法執(zhí)行完成之后執(zhí)行,分類的+load會在主類執(zhí)行之后執(zhí)行。不可繼承,子類沒有實現(xiàn)的時候,文件加載的時候是不會調(diào)用父類的load方法的。那么為什么+load的方法會有這樣的特性,runtime又有哪些巧妙的處理呢。今天我們來刨根問底一下+load方法。
??首先解釋兩個變量和兩個結(jié)構(gòu)體,可以先看一眼有個印象,后面遇到再回來仔細察看

//每次調(diào)用add_class_to_loadable_list()方法都會++,記錄這個方法的調(diào)用次數(shù),也相當(dāng)于Class中+load方法列表的個數(shù) 
static int loadable_classes_used = 0;
//同上,只不過對應(yīng)的是add_category_to_loadable_list()方法和Category中的+load方法列表
static int loadable_categories_used = 0;
//存儲了+load方法所屬的Class和+load方法的IMP
struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};
//同上,只不過對應(yīng)的是Category
struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};
//存儲Class和categoty這兩個struct數(shù)據(jù)的是數(shù)組

1. Category方法列表的裝載

1.1 調(diào)用棧

??我們還是通過runtime的源碼來分析這個問題首先看分類的方法是什么時候加到主類的methed_list_t列表中的。下圖為調(diào)用棧:
調(diào)用棧.png

??可以看到調(diào)用次序是這樣的,我們重點看_read_images方法。

map_images() —>map_images_nolock()—>_read_images()
1.2 _read_images()

??源碼較長,這里我只貼對我們有用的部分

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//前面的都省略
// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
//后面的也省略
}

??注意到里面的addUnattachedCategoryForClass方法了嗎,OC里面都喜歡這種見文知義的命名方法,直譯過來就是為類添加未附加的類別。在這個方法里面,會找到未添加的類別列表對Class和Category做一個映射關(guān)聯(lián)。而在remethodizeClass()方法里面會調(diào)用attachCategories把Category中的方法列表加到Class的methed_list_t里面去。而且是插入到Class方法列表的前面(這就是Category中重寫主類的方法導(dǎo)致的方法覆蓋的原因)

2. +load方法的調(diào)用

2.1 調(diào)用棧

??我們接著來看+load的方法的調(diào)用,如下圖所示,這些會在Category方法裝載之后!下圖為調(diào)用棧

屏幕快照 2018-08-20 下午9.56.36.png

2.2 load_images()

??load_images()方法會多次調(diào)用(每個類都會調(diào)用一次)。我們來看load_images()方法的具體內(nèi)容

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

??里面有prepare_load_methods()和call_load_methods()這兩個主要方法,先來看第一個

2.3 prepare_load_methods()
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]));
    }
#pragma mark - 我是分割線—————————————————————————————————
    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);
    }
}

??這里面又分兩個重要的部分,上面分割線之前是Class中的+load方法加入到對應(yīng)的list中去,那怎么保證的父類先調(diào)用呢

2.3.1 schedule_class_load() & add_class_to_loadable_list()
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); 
}
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    //往數(shù)組中添加loadable_classes結(jié)構(gòu)體,并賦值
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

??schedule_class_load()方法做了遞歸調(diào)用一直調(diào)用到superclass為空,在schedule_class_load()方法中會調(diào)用add_class_to_loadable_list。這樣就保證了父類的+load方法是加載到list前面的,從父類到子類依次往數(shù)組中添加。執(zhí)行的時候也是從前往后遍歷數(shù)組調(diào)用。
??add_class_to_loadable_list()方法的實現(xiàn),我們可以很清楚的看到loadable_classes的初始化策略與溢出時的擴容策略。每次需要擴容都會在原來的基礎(chǔ)之上*2+16。

2.3.2 add_category_to_loadable_list()
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }
//往數(shù)組中添加loadable_categories結(jié)構(gòu)體,并賦值
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

??我們來看分割線之后的部分,在Class的+load方法處理完成時候才會來處理Category中的+load方法,這里用到了add_category_to_loadable_list()可以看到與add_class_to_loadable_list()方法的內(nèi)容基本一樣只是標志位和數(shù)組的名字不同而已。add_category_to_loadable_list()在Category中的+load方法加入到對應(yīng)的列表中。至此prepare_load_methods()方法執(zhí)行完畢。

2.4 call_load_methods()

??接下來我們來看call_load_methods()方法的具體內(nèi)容

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
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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;
}

??看代碼這里還用到了aotureleasePool,這個是題外話了,撇開不談。我們來看今天的重點,首先是一個do while循環(huán),循環(huán)條件有兩個。第一個loadable_classes_used(查看文章開頭),第二個是call_category_loads()方法的返回值,這個返回值是什么呢,其實就是loadable_categories_used>0。在這里先后調(diào)用所有Class列表中的+load方法,Category列表中的+load方法call_class_loads()和call_category_loads()兩個方法的代碼我就不貼了。代碼有點多,影響閱讀,有興趣可以去看runtime的源碼。

3. 總結(jié)與問題

3.1總結(jié)

??分析下來,基本回答了文章開頭所述的+load方法特征的原因??偨Y(jié)如下:
??1、Category方法列表的裝載是在_read_images的時候發(fā)生的,這個調(diào)用比較早在map_images之后,load_images前。
??2、+load方法的調(diào)用是在load_images是發(fā)生的,而且load_image會重復(fù)調(diào)用(每個類都會調(diào)用)。而load_images調(diào)在_read_images之后,也就是說是在Category中的方法插入到Class中的方法列表之后調(diào)用的。
??3、+load方法的調(diào)用是又專門的方法負責(zé)的,Class和Category分別有一個數(shù)組保存+load方法(數(shù)組內(nèi)保存的是loadable_class,和loadable_category結(jié)構(gòu)體參考文章開頭的介紹)。所以不會被Category方法的裝載導(dǎo)致方法覆蓋。
??4、Class的數(shù)組,加入的次序也是有保證的,從最高級的父類到子類一次加入,調(diào)用的時候能保證先父類后子類。
??5、Category數(shù)組,調(diào)用是在Class中的方法列表調(diào)用完成之后,保證了次序。

3.2問題

??call_class_loads()的實現(xiàn)比較簡單,就是一個for循環(huán)依次調(diào)用+load。call_category_loads()內(nèi)部的實現(xiàn)就復(fù)雜了不少,還有許多復(fù)雜的代碼,筆者也是沒有太看懂內(nèi)部的那些判斷和循環(huán),也不敢在這里誤人子弟。如果有哪位明白的請不吝指教。

最后編輯于
?著作權(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)容

  • 原文鏈接: http://draveness.me/load/關(guān)注倉庫,及時獲得更新:iOS-Source-Cod...
    Draveness閱讀 4,005評論 4 35
  • 一、基本使用 1、RevanPerson類 2、RevanPerson+RevanRun 3、RevanPerso...
    紫荊秋雪_文閱讀 665評論 0 1
  • 該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請注明:劉小壯[http://www.itdecent.cn/u/2de707c93d...
    劉小壯閱讀 5,845評論 11 16
  • 阿黃是四伯家里養(yǎng)的一條雄性大黃狗,全身毛茸茸的,它打架是那種沒用的類型,因為不夠有過,只會汪汪的大叫而已,還不如一...
    選妃閱讀 236評論 2 0
  • 易允強~微信企劃~利他組 【日精進打卡第165天】 【知~學(xué)習(xí)】 《六項精進》2遍 共228遍 《大學(xué)》2遍 共2...
    云自閑閱讀 122評論 0 0

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