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

load_images

  • 進入 load_images 源碼實現,如下
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 的源碼翻伺,實現如下
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 的源碼實現如下
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 表中,源碼實現如下

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 的函數指針点楼。

    1. 通過 _getObjc2NonlazyCategoryList 獲取非懶加載分類列表扫尖,循環(huán)遍歷每個分類。realizeClassWithoutSwift -> add_category_to_loadable_list 將分類的 load 方法加入 loadable_categories 表中掠廓。add_category_to_loadable_list 的源碼實現如下
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

源碼實現如下

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 方法蟀瞧,直到沒有

call_class_loads 的源碼實現如下

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

call_category_loads 的源碼實現如下

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

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

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

Category 類別、分類

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

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

  • 分類中用 @property 定義變量,只會生成變量的 setter昌屉,getter 方法的聲明钙蒙,不能生成方法實現和帶下劃線的成員變量

Extension 類擴展

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

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

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

類擴展的底層原理探索

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

    1. 創(chuàng)建一個 NSObject 類,直接在類中(.m 文件)添加代碼 (只能在類的聲明之后蜻牢,實現之前添加)
    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 類,并實現擴展(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 中實現的四個方法

  • 導入 LGPerson+Ext.h

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

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

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

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

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子榔幸,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柄粹,死亡現場離奇詭異,居然都是意外死亡键畴,警方通過查閱死者的電腦和手機最盅,發(fā)現死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來起惕,“玉大人涡贱,你說我怎么就攤上這事∪窍耄” “怎么了问词?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嘀粱。 經常有香客問我激挪,道長,這世上最難降的妖魔是什么锋叨? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任垄分,我火速辦了婚禮,結果婚禮上娃磺,老公的妹妹穿的比我還像新娘薄湿。我一直安慰自己,他們只是感情好偷卧,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布豺瘤。 她就那樣靜靜地躺著,像睡著了一般听诸。 火紅的嫁衣襯著肌膚如雪坐求。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天蛇更,我揣著相機與錄音瞻赶,去河邊找鬼。 笑死派任,一個胖子當著我的面吹牛砸逊,可吹牛的內容都是我干的。 我是一名探鬼主播掌逛,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼师逸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了豆混?” 一聲冷哼從身側響起篓像,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤动知,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后员辩,有當地人在樹林里發(fā)現了一具尸體盒粮,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年奠滑,在試婚紗的時候發(fā)現自己被綠了丹皱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡宋税,死狀恐怖摊崭,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情杰赛,我是刑警寧澤呢簸,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站乏屯,受9級特大地震影響根时,放射性物質發(fā)生泄漏。R本人自食惡果不足惜辰晕,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一啸箫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伞芹,春花似錦忘苛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至南缓,卻和暖如春胸遇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背汉形。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工纸镊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人概疆。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓逗威,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岔冀。 傳聞我的和親對象是個殘疾皇子凯旭,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容