iOS class_ro_t和class_rw_t的區(qū)別 category加載過程解析

本文主要介紹class_ro_t和class_rw_t的區(qū)別、分類加載過程以及多個(gè)分類加載的問題

class_ro_t

class_ro_t存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性方法以及遵循的協(xié)議,里面是沒有分類的方法的。那些運(yùn)行時(shí)添加的方法將會(huì)存儲(chǔ)在運(yùn)行時(shí)生成的class_rw_t中。

ro即表示read only,是無法進(jìn)行修改的。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
}

class_rw_t

ObjC 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t中:

// 可讀可寫
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro; // 指向只讀的結(jié)構(gòu)體,存放類初始信息

    /*
     這三個(gè)都是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容。
     methods中,存儲(chǔ) method_list_t ----> method_t
     二維數(shù)組,method_list_t --> method_t
     這三個(gè)二位數(shù)組中的數(shù)據(jù)有一部分是從class_ro_t中合并過來的。
     */
    method_array_t methods; // 方法列表(類對(duì)象存放對(duì)象方法,元類對(duì)象存放類方法)
    property_array_t properties; // 屬性列表
    protocol_array_t protocols; //協(xié)議列表

    Class firstSubclass;
    Class nextSiblingClass;
    
    //...
    }

class_rw_t生成時(shí)機(jī)

class_rw_t生成在運(yùn)行時(shí),在編譯期間,class_ro_t結(jié)構(gòu)體就已經(jīng)確定,objc_class中的bits的data部分存放著該結(jié)構(gòu)體的地址。在runtime運(yùn)行之后,具體說來是在運(yùn)行runtimerealizeClass方法時(shí),會(huì)生成class_rw_t結(jié)構(gòu)體,該結(jié)構(gòu)體包含了class_ro_t,并且更新data部分,換成class_rw_t`結(jié)構(gòu)體的地址。

類的realizeClass運(yùn)行之前:

在這里插入圖片描述

類的realizeClass運(yùn)行之后:
在這里插入圖片描述

細(xì)看兩個(gè)結(jié)構(gòu)體的成員變量會(huì)發(fā)現(xiàn)很多相同的地方,他們都存放著當(dāng)前類的屬性、實(shí)例變量、方法、協(xié)議等等。區(qū)別在于:class_ro_t存放的是編譯期間就確定的;而class_rw_t是在runtime時(shí)才確定,它會(huì)先將class_ro_t的內(nèi)容拷貝過去,然后再將當(dāng)前類的分類的這些屬性、方法等拷貝到其中。所以可以說class_rw_tclass_ro_t的超集,當(dāng)然實(shí)際訪問類的方法、屬性等也都是訪問的class_rw_t中的內(nèi)容

摘自 http://www.lxweimin.com/p/823eaedb3697

分類方法加載到class_rw_t的流程

  • 程序啟動(dòng)后,通過編譯之后,Runtime 會(huì)進(jìn)行初始化,調(diào)用 _objc_init
_objc_init`由`dyld`驅(qū)動(dòng),這個(gè)階段會(huì)注冊(cè)`3`個(gè)回調(diào),分別是`mapped`,`init`,`unmapped
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

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(); //環(huán)境調(diào)試 例如僵尸模式設(shè)置后,就是在這里起作用的
    tls_init(); //tls指的是局部線程存儲(chǔ),可以將數(shù)據(jù)存儲(chǔ)在線程一個(gè)公共區(qū)域,例如pthread_setspecific(),在autoreleasepool和堆棧信息獲取時(shí)都有涉及
    static_init(); //執(zhí)行c++靜態(tài)構(gòu)造函數(shù)
    lock_init(); //這里獲取兩個(gè)的線程優(yōu)先級(jí) 后臺(tái)優(yōu)先級(jí)線程以及主線程
    exception_init(); //這里初始化libobjc的exception處理系統(tǒng)

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
123456789101112131415161718192021

比較核心的是_dyld_objc_notify_register方法

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
1

map_images

  • 然后會(huì) map_images
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
1234567
  • 接下來調(diào)用map_images_nolock
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    //.... 略去一大塊
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    //...
}

1234567891011
  • 再然后就是 _read_images,這個(gè)方法會(huì)讀取所有的類的相關(guān)信息。根據(jù)注釋,_read_images 方法主要做了下面這些事情:

_read_images 方法寫了很長(zhǎng),其實(shí)就是做了一件事,將Mach-O文件的section依次讀取,并根據(jù)內(nèi)容初始化runtime的內(nèi)存結(jié)構(gòu)。

  1. 是否需要禁用isa優(yōu)化。這里有三種情況:使用了swift 3.0前的swift代碼。OSX版本早于10.11。在OSX系統(tǒng)下,Mach-ODATA段明確指明了__objc_rawisa(不使用優(yōu)化的isa).

蘋果從ARM64位架構(gòu)開始,對(duì)isa進(jìn)行了優(yōu)化,將其定義成一個(gè)共用體(union)結(jié)構(gòu),結(jié)合 位域 的概念以及 位運(yùn)算 的方式來存儲(chǔ)更多類相關(guān)信息。isa指針需要通過與一個(gè)叫ISA_MASK的值(掩碼)進(jìn)行二進(jìn)制&運(yùn)算,才能得到真實(shí)的class/meta-class對(duì)象的地址。
參考文章 http://www.lxweimin.com/p/30de582dbeb7

  1. 判斷是否禁用了tagged pointer
  2. __objc_classlist section中讀取class list
  3. __objc_classrefs section中讀取class 引用的信息,并調(diào)用remapClassRef方法來處理。
  4. __objc_selrefs section中讀取selector的引用信息,并調(diào)用sel_registerNameNoLock方法處理。
  5. __objc_protolist section中讀取clsProtocol信息,并調(diào)用readProtocol方法來讀取Protocol信息。
  6. __objc_protorefs section中讀取protocol的ref信息,并調(diào)用remapProtocolRef方法來處理。
  7. __objc_nlclslist section中讀取non-lazy class信息,并調(diào)用static Class realizeClass(Class cls)方法來實(shí)現(xiàn)這些class。realizeClass方法核心是初始化objc_class數(shù)據(jù)結(jié)構(gòu),賦予初始值。
  8. __objc_catlist section中讀取category信息,并調(diào)用addUnattachedCategoryForClass方法來為類或元類添加對(duì)應(yīng)的方法,屬性和協(xié)議。
  • 調(diào)用 reMethodizeClass:,這個(gè)方法是重新方法化的意思。
  • reMethodizeClass:方法內(nèi)部會(huì)調(diào)用attachCategories: ,這個(gè)方法會(huì)傳入 ClassCategory,會(huì)將方法列表,協(xié)議列表等與原有的類合并。最后加入到 class_rw_t 結(jié)構(gòu)體中。

load_images

構(gòu)造好 class_rw_t 之后,load_images 調(diào)用 call_load_methods 就是開始調(diào)用類的+load方法和分類的+load方法了

/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first. 
* Category +load methods are not called until after the parent class's +load.
* 
* This method must be RE-ENTRANT, because a +load could trigger 
* more image mapping. In addition, the superclass-first ordering 
* must be preserved in the face of re-entrant calls. Therefore, 
* only the OUTERMOST call of this function will do anything, and 
* that call will handle all loadable classes, even those generated 
* while it was running.
*
* The sequence below preserves +load ordering in the face of 
* image loading during a +load, and make sure that no 
* +load method is forgotten because it was added during 
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
*    (a) there are more classes to load, OR
*    (b) there are some potential category +loads that have 
*        still never been attempted.
* Category +loads are only run once to ensure "parent class first" 
* ordering, even if a category +load triggers a new loadable class 
* and a new loadable category attached to that class. 
*
* Locking: loadMethodLock must be held by the caller 
*   All other locks must not be held.
**********************************************************************/
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;
}

unmap_image

unmap_image` 調(diào)用 `_unload_image

涉及一些資源的釋放,例如 unattached list+load queue,將每個(gè)類分離后,進(jìn)行釋放

/***********************************************************************
* _unload_image
* Only handles MH_BUNDLE for now.
* Locking: write-lock and loadMethodLock acquired by unmap_image
**********************************************************************/
void _unload_image(header_info *hi)
{
    size_t count, i;

    loadMethodLock.assertLocked();
    runtimeLock.assertWriting();

    // Unload unattached categories and categories waiting for +load.

    category_t **catlist = _getObjc2CategoryList(hi, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = catlist[i];
        if (!cat) continue;  // category for ignored weak-linked class
        Class cls = remapClass(cat->cls);
        assert(cls);  // shouldn't have live category for dead class

        // fixme for MH_DYLIB cat's class may have been unloaded already

        // unattached list
        removeUnattachedCategoryForClass(cat, cls);

        // +load queue
        remove_category_from_loadable_list(cat);
    }

    // Unload classes.

    // Gather classes from both __DATA,__objc_clslist 
    // and __DATA,__objc_nlclslist. arclite's hack puts a class in the latter
    // only, and we need to unload that class if we unload an arclite image.

    NXHashTable *classes = NXCreateHashTable(NXPtrPrototype, 0, nil);
    classref_t *classlist;

    classlist = _getObjc2ClassList(hi, &count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if (cls) NXHashInsert(classes, cls);
    }

    classlist = _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if (cls) NXHashInsert(classes, cls);
    }

    // First detach classes from each other. Then free each class.
    // This avoid bugs where this loop unloads a subclass before its superclass

    NXHashState hs;
    Class cls;

    hs = NXInitHashState(classes);
    while (NXNextHashState(classes, &hs, (void**)&cls)) {
        remove_class_from_loadable_list(cls);
        detach_class(cls->ISA(), YES);
        detach_class(cls, NO);
    }
    hs = NXInitHashState(classes);
    while (NXNextHashState(classes, &hs, (void**)&cls)) {
        free_class(cls->ISA());
        free_class(cls);
    }

    NXFreeHashTable(classes);
    
    // XXX FIXME -- Clean up protocols:
    // <rdar://problem/9033191> Support unloading protocols at dylib/image unload time

    // fixme DebugUnload
}

兩個(gè)category的load方法的加載順序,兩個(gè)category的同名方法的加載順序

+load 方法是 images 加載的時(shí)候調(diào)用,假設(shè)有一個(gè) Person 類,其主類和所有分類的 +load 都會(huì)被調(diào)用,優(yōu)先級(jí)是先調(diào)用主類,且如果主類有繼承鏈,那么加載順序還必須是基類的 +load ,接著是父類,最后是子類;category 的 +load 則是按照編譯順序來的,先編譯的先調(diào)用,后編譯的后調(diào)用,可在 Xcode 的 BuildPhase 中查看,測(cè)試 Demo 可點(diǎn)擊下載運(yùn)行

另外一個(gè)問題是 initialize 的加載順序,其實(shí)是類第一次被使用到的時(shí)候會(huì)被調(diào)用,底層實(shí)現(xiàn)有個(gè)邏輯先判斷父類是否被初始化過,沒有則先調(diào)用父類,然后在調(diào)用當(dāng)前類的 initialize 方法;試想一種情況,一個(gè)類 A 存在多個(gè) category ,且 category中各自實(shí)現(xiàn)了 initialize 方法,這時(shí)候走的是 消息發(fā)送流程,也就說 initialize 方法只會(huì)調(diào)用一次,也就是最后編譯的那個(gè)category中的 initialize 方法,驗(yàn)證demo見上;

再考慮一種情況:如果+load 方法中調(diào)用了其他類:比如 B 的某個(gè)方法,其實(shí)說白了就是走消息發(fā)送流程,由于 B 沒有初始化過,則會(huì)調(diào)用其 initialize 方法,但此刻 B 的 +load 方法可能還沒有被系統(tǒng)調(diào)用過。

小結(jié): 不管是 load 還是 initialize 方法都是 runtime 底層自動(dòng)調(diào)用的,如果開發(fā)自己手動(dòng)進(jìn)行了 [super load] 或者 [super initialize] 方法,實(shí)際上是走消息發(fā)送流程,那么這里也涉及了一個(gè)調(diào)用流程,需要引起注意。

... -> realizeClass -> methodizeClass(用于Attach categories)-> attachCategories 關(guān)鍵就是在 methodizeClass 方法實(shí)現(xiàn)中

static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;
    
    // =======================================
        // 省略.....
    // =======================================
  
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    // =======================================
        // 省略.....
    // =======================================

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);

    // =======================================
        // 省略.....
    // =======================================
    
    if (cats) free(cats);

}

上面代碼能確定 baseProperties 在前,category 在后,但決定順序的是 rw->properties.attachLists 這個(gè)方法:

property_list_t *proplist = ro->baseProperties;
if (proplist) {
  rw->properties.attachLists(&proplist, 1);
}

/// category 被附加進(jìn)去
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            
            // 將舊內(nèi)容移動(dòng)偏移量 addedCount 然后將 addedLists copy 到起始位置
            /*
                struct array_t {
                        uint32_t count;
                        List* lists[0];
                        };
            */
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

所以 category 的屬性總是在前面的,baseClass的屬性被往后偏移了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容