iOS load方法調用機制解析

前言:git

上篇文章 iOS Category底層原理詳細研究流程 中 , 咱們有說到編譯時 dyld 解讀 Mach-o 文件對應在 runtime 庫的入口函數 _objc_init 中三種不一樣時期對應的回調函數.github

那麼一樣 , 咱們探索 load 方法也是由此展開 . 也就是在 dyld 讀取完成時會調用 load_images 方法 , load 方法也是在此時調用. 感興趣的能夠去解讀一下上篇博客.數組

廢話很少說 , 一樣打開 objc4 的源碼 ( git開源地址 )app

探索流程

1. runtime 庫入口函數

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼

關於 map_images, load_images, unmap_image 這三個函數我上一篇博客有具體提到, 這裏很少贅述了. 直接進去 load_images :函數

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
    {
        mutex_locker_t lock2(runtimeLock);
        /** 準備 */
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
複製代碼

代碼中看出這裏分爲了兩步 , 先準備 , 而後調用 . --> GOpost

2. load 方法準備

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

    runtimeLock.assertLocked();

    //從 Macho 文件加載類的列表
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //數組:[<cls,method>,<cls,method>,<cls,method>] 有順序
        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);
    }
}
複製代碼

這裏又有兩步操做spa

2.1 類的 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); 
}
複製代碼

其實就是從 Mach-o 中讀取到全部的類的列表 . 而後ssr

  • 遞歸 按當前類的 superclass 指針進行排列. 以此來知足一個規則 :

一個類的 load 方法調用 , 必定是在其父類的 load 方法以後.指針

  • 當遞歸到 NSObject 後 , 再找父類爲 nil , 跳出遞歸 . 結束排列 , 而且將每一個 load 方法的 clsmethod 存儲到全局的 loadable_class 結構體中.

2.2 分類的 load 方法讀取和排序

分類的 load 大致流程和類的基本相似 , 一樣先從 Mach-o 讀取全部的分類列表 , 而後直接遍歷 , 添加分類的 load 方法的 clsmethod 存儲到全局的 loadable_categories 結構體中.code

也就是說 , 不一樣分類之間 load 方法的加載順序是根據 Mach-o 文件的編譯順序有關.

3. load 調用

call_load_methods :

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) {
            //先調用類的 load 方法
            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;
}
複製代碼

這裏就比較簡單了. 先遍歷以前存儲好的類的 load 方法列表. 依次調用. 而後遍歷分類的列表 , 依次調用.

總結:

  • load 方法在 runtime 庫開始運行時調用.
  • 一個類的 load 方法在全部的父類 load 方法調用以後.
  • 分類的 load 方法在類的 load 方法以後.
  • 不一樣分類之間的 load 方法調用順序和編譯順序有關.
相關文章
相關標籤/搜索