iOS 底層原理-類的加載(下)

load_images

  • 進入 load_images 源碼實現(xiàn),如下
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories(); // 加載所有分類
    }

    // 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 // +load 方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

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

load_images 方法的主要作用是加載鏡像文件,其中關鍵代碼為 prepare_load_methods(加載)、call_load_methods(調用),兩個方法

1. prepare_load_methods

  • 進入 prepare_load_methods 的源碼,實現(xiàn)如下
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, I;

    runtimeLock.assertLocked();

    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count); // 獲取 Mach-O 中的靜態(tài)段 __objc_nlclslist 即非懶加載類
    for (i = 0; i < count; i++) {
        // 將所有的 +load 方法與類綁定加入 loadable_classes 表中
        schedule_class_load(remapClass(classlist[i]));
    }

    // 獲取 Mach-O 中的靜態(tài)段 __objc_nlcatlist,即非懶加載分類,并 load 方法加入表中
    category_t * const *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
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
    1. 通過 _getObjc2NonlazyClassList -> schedule_class_load 獲取非懶加載類列表,將所有的 +load 方法與類綁定加入 loadable_classes 表中。schedule_class_load 的源碼實現(xiàn)如下
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
    // 確保父類優(yōu)先加載,遞歸,直到父類不存在
    schedule_class_load(cls->superclass);

    // 將所有的 +load 方法的類跟 load 方法綁定加入 loadable_classes 表中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

其中,schedule_class_load(cls->superclass); 為了確保它的父類優(yōu)先加載,是一層遞歸循環(huán),直到根類(NSObject)的父類(nil)。add_class_to_loadable_list 是將類的 load 方法和 cls 類名一起加到 loadable_classes 表中,源碼實現(xiàn)如下

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod(); // 獲取類的 load 方法
    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));
    }
    // 加入 loadable_classes 表中
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

獲取類的 load 方法的源碼如下

IMP 
objc_class::getLoadMethod()
{
    runtimeLock.assertLocked();
    const method_list_t *mlist;

    ASSERT(isRealized());
    ASSERT(ISA()->isRealized());
    ASSERT(!isMetaClass());
    ASSERT(ISA()->isMetaClass());

    mlist = ISA()->data()->ro()->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            if (0 == strcmp(name, "load")) {
                return meth.imp;
            }
        }
    }

    return nil;
}

通過獲取 ro 中的方法列表,循環(huán)遍歷,直到找到方法名為 loadsel,返回 load 的函數(shù)指針。

    1. 通過 _getObjc2NonlazyCategoryList 獲取非懶加載分類列表,循環(huán)遍歷每個分類。realizeClassWithoutSwift -> add_category_to_loadable_list 將分類的 load 方法加入 loadable_categories 表中。add_category_to_loadable_list 的源碼實現(xiàn)如下
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat); // 獲取分類的 load 方法

    // 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));
    }

    // 添加到 loadable_categories 表中
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

2. call_load_methods

源碼實現(xiàn)如下

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;
}

從源碼注釋中可以看到,關鍵代碼是在一個 do - while 循環(huán)中,主要有三個部分

    1. 反復調用類的 +load 方法,直到?jīng)]有

call_class_loads 的源碼實現(xiàn)如下

    1. 只調用一次分類的 +load 方法

call_category_loads 的源碼實現(xiàn)如下

    1. 如果有類或更多未嘗試的分類,則運行更多的+load

call_class_loadscall_category_loads 中 load 消息發(fā)送 (*load_method)(cls, @selector(load)); 有兩個隱藏參數(shù),第一個為 id 即self,第二個為 sel,即 cmd。

分類(category)與類擴展(extension)

Category 類別、分類

  • 專門用來給分類添加新的方法

  • 不能給類添加成員屬性,添加了成員變量,也無法取到(注意:可以通過 runtime 給分類添加屬性)

  • 分類中用 @property 定義變量,只會生成變量的 setter,getter 方法的聲明,不能生成方法實現(xiàn)和帶下劃線的成員變量

Extension 類擴展

  • 可以說成是特殊的分類,也稱作匿名函數(shù)

  • 可以給類添加成員屬性,但是是私有變量

  • 可以給類添加方法,也是私有方法

類擴展的底層原理探索

創(chuàng)建方式有兩種

    1. 創(chuàng)建一個 NSObject 類,直接在類中(.m 文件)添加代碼 (只能在類的聲明之后,實現(xiàn)之前添加)
    1. 通過新建(command + N)-> Objective-C File

選擇 Extension 類型、選擇要添加拓展的主類,創(chuàng)建

類擴展的本質

寫一個類擴展,如下

通過 clang 底層編譯探索
  • 執(zhí)行 clang -rewrite-objc main.mm -o main.cpp 生成 cpp 文件,打開 cpp 文件,首先我們看下屬性 lg_name,搜索 lg_name,如下

可以看到編譯過程中生成了帶下劃線的成員變量以及 setter、getter 方法。再來看下類擴展中的方法

從上面我們可以得知,在編譯的過程中,類擴展中的方法被添加到 methodlist 中成為了類的一部分,即編譯時期直接添加到本類。

通過源碼探索
  • 創(chuàng)建主類 LGPerson 類,并實現(xiàn)擴展(LGPerson+Ext)中的方法
/** ------.h ------*/
@interface LGPerson : NSObject

@end

/** ------.m ------*/
#import "LGPerson.h"
#import "LGPerson+Ext.h"

@implementation LGPerson

+ (void)load {
    
}

- (void)ext_instanceMethod {
    
}

- (void)ext_classMethod {
    
}

- (void)instanceMethod {
    
}

- (void)classMethod {
    
}

@end

/** ------ LGPerson+Ext.h ------*/
#import "LGPerson.h"

@interface LGPerson ()

@property (nonatomic, copy) NSString *lg_name;

- (void)ext_instanceMethod;
- (void)ext_classMethod;

@end
  • 不導入 LGPerson+Ext.h

  • 運行 objc 源碼,在 readClass 打個斷點,查看此時的 ro 情況

可以看到此時有四個方法,分別打印出來

可以看到此時打印的是 LGPerson.m 中實現(xiàn)的四個方法

  • 導入 LGPerson+Ext.h

按照上面的再來一次,看看此時的 ro 情況

方法列表中有 7 個方法,分別打印出來

此時在拓展類(LGPerson+Ext.h)聲明的屬性也實現(xiàn) setter、getter 方法以及一個 .cxx 方法

類的擴展在編譯期間會作為類的一部分,和類一起編譯進來
類的擴展只是聲明,依賴于當前的主類,沒有.m文件

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

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

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