iOS類的加載原理(下)

前言

我們?cè)?a href="http://www.itdecent.cn/p/b2a845340192" target="_blank">iOS類的加載原理(上)我們介紹了類的加載原理的大致流程,由篇幅過(guò)長(zhǎng),上篇文章未介紹完,這篇文章繼續(xù)展開(kāi)類的加載原理分析。

rwe,rw,ro的介紹

我們?cè)?a href="http://www.itdecent.cn/p/b2a845340192" target="_blank">iOS類的加載原理(上)簡(jiǎn)單介紹了,rwe,rw,ro,那么為什么會(huì)這些呢?
clean memory是指加載后不會(huì)發(fā)生更改的內(nèi)存,可以進(jìn)行移除從而節(jié)省更多的內(nèi)存空間

dirty memory是指在進(jìn)程運(yùn)行時(shí)會(huì)發(fā)生更改的內(nèi)存,類結(jié)構(gòu)一經(jīng)使用就會(huì)變 成dirty memory,因?yàn)檫\(yùn)行時(shí)會(huì)向它寫(xiě)入新的數(shù)據(jù),比如:創(chuàng)建一個(gè)新的方法緩存并從類中指向它,它比clean memory要昂貴得多,只要進(jìn)程在運(yùn)行,它就必須一直存在,,因?yàn)槿绻阈枰猚lean memory,系統(tǒng)可以從磁盤(pán)中重新加載,macOS可以選擇喚出dirty memory,因?yàn)閕OS不使用swap所以diry memory在iOS中代價(jià)很大,dirty memory是這個(gè)類數(shù)據(jù)被分成兩部分的原因,可以保持清的數(shù)據(jù)越多越好,可以保持清潔的數(shù)據(jù)越多越好,通過(guò)分離那些永遠(yuǎn)不會(huì)更改的數(shù)據(jù),可以把大部分的類數(shù)據(jù)存在clean memory

ro(class_ro_t)屬于clean memory,因?yàn)樗侵蛔x的,從磁盤(pán)加載進(jìn)來(lái)的(沙盒)

rw(class_rw_t)屬于dirty memory, 當(dāng)類第一次使用的時(shí)候運(yùn)行時(shí)會(huì)為類分配額外的存儲(chǔ)容量,這個(gè)運(yùn)行時(shí)分配的存儲(chǔ)容量是class_rw_t用于讀取-編寫(xiě)數(shù)據(jù),在這個(gè)數(shù)據(jù)結(jié)構(gòu)中,存儲(chǔ)了只在在運(yùn)于時(shí)才會(huì)生成的新信息,比如:所有的類都會(huì)鏈接成一個(gè)樹(shù)狀結(jié)構(gòu),通過(guò)First Subclass和Next Sibling Class指針實(shí)現(xiàn)的,這允許運(yùn)行時(shí)遍歷當(dāng)前使用的所有類,這對(duì)于使方法緩存無(wú)效非常有用,它的數(shù)據(jù)是從ro中復(fù)制過(guò)來(lái)的 drity memory是在手機(jī)端是非常昂貴的

rwe 當(dāng)category加載,添加新的方法時(shí),需要用到運(yùn)行時(shí),由ro只讀,我們無(wú)法更改,就需要rw,就會(huì)占用相當(dāng)多的內(nèi)存,不可能所有的類都會(huì)需要?jiǎng)討B(tài)修改,如果全部寫(xiě)到rw就會(huì)浪費(fèi)很多內(nèi)存,當(dāng)我們需要rwe的時(shí)候,就會(huì)根據(jù)一個(gè)ext的標(biāo)識(shí)創(chuàng)建,對(duì)rw做一些擴(kuò)展,優(yōu)化內(nèi)存,像我們的方法,協(xié)議,屬性會(huì)有變化的可能,所以存在rwe中,而類的父類信息,名字放在rw中。

指針強(qiáng)轉(zhuǎn)到數(shù)據(jù)結(jié)構(gòu)

iOS類的加載原理(上)中我們介紹read_class中有cls->data()可以得到data的數(shù)據(jù),那么data的數(shù)據(jù)從哪來(lái),我們接著分析。
我們搜索realizeClassWithoutSwift找到相關(guān)實(shí)現(xiàn),我們找到下面兩行代碼:

 auto ro = (const class_ro_t *)cls->data();
 auto isMeta = ro->flags & RO_META;

我們運(yùn)行并斷點(diǎn)調(diào)試一下,如圖:


1

cls是一個(gè)地址,調(diào)用了data()就得到了ro這是為什么呢?我們接著往下看。
我們看下data這個(gè)函數(shù)的源碼

 class_rw_t *data() const {
        return bits.data();
    }

是從bits里面拿到的data我們?cè)倏聪耣its,如下:

class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

這里bits& FAST_DATA_MASK這個(gè)值強(qiáng)轉(zhuǎn)成了指向class_rw_t類型的指針,結(jié)構(gòu)體指針可以取結(jié)構(gòu)中的數(shù)據(jù)。

attachCategories反推思路

我們上面介紹了rwe,rw,ro那么 rwe是在什么時(shí)候有賦值的呢,我們繼續(xù)分析。
methodizeClass這個(gè)函數(shù)中auto rwe = rw->ext();由這行代碼控制rwe,經(jīng)過(guò)查找extAllocIfNeeded由這個(gè)函數(shù)控制,iOS類的加載原理(上)里面有介紹extAllocIfNeeded這個(gè)函數(shù)的賦值的點(diǎn)在attachCategories,而它又在attachToClassload_categories_nolock有調(diào)用。
我們先在attachToClass打個(gè)斷點(diǎn),我們?cè)僬?strong>attachToClass的調(diào)用點(diǎn),結(jié)果發(fā)現(xiàn)是在methodizeClass調(diào)用的,我們先看下里面調(diào)用的代碼:

// Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

attachToClass的調(diào)用是由previously控制的,我們發(fā)現(xiàn)previously是methodizeClass這個(gè)函數(shù)的形參,我們?cè)俅尾檎野l(fā)現(xiàn)是在realizeClassWithoutSwift這里調(diào)用了methodizeClass并傳入previously參數(shù),realizeClassWithoutSwift的previously也是由形參傳過(guò)來(lái)的,經(jīng)過(guò)一系的分析,它是一個(gè)備用參數(shù)(nil),這里不會(huì)調(diào)用,
我們?cè)倏聪聫垐D:

2

只會(huì)調(diào)用1559的attachToClass。
我們的推導(dǎo)過(guò)程順序如下:
realizeClassWithoutSwift-> methodizeClass-> attachToClass-> attachCategories

attachList算法

我們看下** attachCategories**的代碼,如下:

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
    
    const char *mangledName = cls->nonlazyMangledName();
    if (strcmp(mangledName, "LGPerson") == 0)
    {
        if (!isMeta) {
            printf("LGPerson....\n");
        }
    }

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[I];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

這里面調(diào)用的rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
我們看下attachLists這個(gè)函數(shù),代碼如下:

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;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I];
            for (unsigned i = 0; i < addedCount; I++)
                newArray->lists[i] = addedLists[I];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; I++)
                array()->lists[i] = addedLists[I];
            validate();
        }
    }

這是核心重點(diǎn),我們來(lái)分析下。

  else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 

這個(gè)流程把a(bǔ)ddedLists[0]給了List,list是一維數(shù)組。

else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; I++)
                array()->lists[i] = addedLists[I];
            validate();
        }

這段代碼就是如果oldList為1,在lists的第addedCount元素插入數(shù)據(jù),接著再把a(bǔ)ddedLists元素從lists的0元素開(kāi)始插入。

   if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I];
            for (unsigned i = 0; i < addedCount; I++)
                newArray->lists[i] = addedLists[I];
            free(array());
            setArray(newArray);
            validate();
        }

這段代碼倒序一個(gè)一個(gè)的插入到newArray中,最后會(huì)把newArray釋放掉 。

分類和主類加載的4種情況

我們添加一個(gè)RoPerson的分類RoPerson+RoA,并且寫(xiě)一個(gè)Load方法,驗(yàn)證是否是必須用Load方法才可以調(diào)用attachCategories。
同樣需要加入以下代碼,方便測(cè)試,如下:

  const char *mangledName = cls->nonlazyMangledName();
    if (strcmp(mangledName, "RoPerson") == 0)
    {
        if (!isMeta) {
            printf("RoPerson....\n");
        }
    }

然后我們?cè)?strong>attachCategories的上面測(cè)試代碼中打斷點(diǎn)調(diào)試,如圖:

3

我們的斷點(diǎn)進(jìn)入了attachCategories函數(shù)中。
1.RoPerson與其分類都有實(shí)現(xiàn)load方法(非懶加載)。
2.分類實(shí)現(xiàn)Load方法,主類不實(shí)現(xiàn)Load方法,經(jīng)過(guò)測(cè)試并沒(méi)有進(jìn)入attachCategories函數(shù)中。
3.分類沒(méi)有實(shí)現(xiàn)Load方法,主類有實(shí)現(xiàn)Load方法,經(jīng)過(guò)測(cè)試并沒(méi)有進(jìn)入attachCategories函數(shù)中。
4.分類沒(méi)有實(shí)現(xiàn)Load方法,主類也沒(méi)有實(shí)現(xiàn)Load方法,經(jīng)過(guò)測(cè)試并沒(méi)有進(jìn)入attachCategories函數(shù)中。
那么分類加載的流程是什么,我們接著分析。

分類加載流程跟蹤

我們先講上面的第一種情況RoPerson與其分類都有實(shí)現(xiàn)load方法。的流程。
我們?cè)?strong>_read_images這個(gè)函數(shù)加入的測(cè)試代碼中斷點(diǎn),如下圖:

4

然后斷點(diǎn)進(jìn)入realizeClassWithoutSwift函數(shù),在終端執(zhí)行以下命令,如圖:
5

當(dāng)我們?nèi)〉降?0的時(shí)候,報(bào)錯(cuò)了,也就是說(shuō)分類的方法,我們繼續(xù)往下執(zhí)行,走到了load_categories_nolock這個(gè)函數(shù)中。
我們看下當(dāng)前的堆棧,如圖:
6

接著我們?cè)诮K端在執(zhí)行以下命令,如圖:
7

在這是里打印出了我們的RoA這個(gè)分類,說(shuō)明加載了,也就是在運(yùn)行時(shí)的時(shí)候才加載。
這個(gè)時(shí)候,再繼續(xù)執(zhí)行,走到了attachCategories這個(gè)函數(shù)。
在以下代碼可以看出在處理方法,協(xié)議,屬性

 method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
 if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

這里對(duì)方法又進(jìn)行了排序,mlists + ATTACH_BUFSIZ - mcount這里得到是二級(jí)指針,通過(guò)p mlists + ATTACH_BUFSIZ - mcount可以查看。
** rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);**這個(gè)函數(shù)把分類的方法加入到主類中。

多個(gè)分類加載

我們?cè)贋镽oPerson創(chuàng)建一個(gè)分類RoPerson+RoB,然后加入方法和屬性,重新運(yùn)行。
在進(jìn)入load_categories_nolock這個(gè)函數(shù)是,我們打印下count變量是2,如圖:

8

然后執(zhí)行以下命令,如圖:
9

這里是RoB的加載,我們過(guò)掉,繼續(xù)執(zhí)行,然后在load_categories_nolock中打印以下命令,如圖:
10

這里是RoA的加載,而這個(gè)時(shí)候在attachLists這個(gè)函數(shù)中走到了

 if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I];
            for (unsigned i = 0; i < addedCount; I++)
                newArray->lists[i] = addedLists[I];
            free(array());
            setArray(newArray);
            validate();
        }

這段代碼,newArray也是二維存儲(chǔ),存的是分類以及主類方法的的指針。

五種情況ro-rw數(shù)據(jù)的加載

以上幾個(gè)是分類與主類都有l(wèi)oad的方法實(shí)現(xiàn)時(shí)的數(shù)據(jù)加載,那么其它的情況呢,我們接著分析。第一種情況以上已經(jīng)介紹。
2.我們先看分類實(shí)現(xiàn),主類沒(méi)有的情況,重新運(yùn)行工程。
如圖:

11

這里進(jìn)入了非懶加載的方式,也就是說(shuō)分類實(shí)現(xiàn)的load方法,導(dǎo)致主類進(jìn)入了非懶加載,那么主類的數(shù)據(jù)怎么來(lái)呢,我們斷點(diǎn)進(jìn)入realizeClassWithoutSwift這個(gè)函數(shù)查看,如圖:
12

這說(shuō)明主類與分類的數(shù)據(jù)已經(jīng)存在,伴隨著類的加載合到一起了,在data()獲取。
3.我們?cè)賮?lái)看分類沒(méi)有,主類有Load方法的實(shí)現(xiàn)情況,重新運(yùn)行工程,經(jīng)過(guò)分析,如圖:
13

這說(shuō)明主類實(shí)現(xiàn)了load方法,分類的方法也有了,同樣在data()獲取數(shù)據(jù)。
4.我們繼續(xù)看主類沒(méi)有實(shí)現(xiàn)load方法,分類也沒(méi)有實(shí)現(xiàn)Load方法的情況,重新運(yùn)行工程,先在main函數(shù)打個(gè)斷點(diǎn),如圖:
14

繼續(xù)執(zhí)行走到了realizeClassWithoutSwift這個(gè)函數(shù),是在第一次發(fā)送消息的時(shí)候加載的(懶加載),我們?cè)倏聪露褩?br>
15

是推遲到第一次發(fā)送消息的時(shí)候加載。
5.主類有Load方法,分類也有l(wèi)oad方法,但是有多個(gè)分類的情況(分類不全有),我們創(chuàng)建RoPerson+RoB這個(gè)分類,重新運(yùn)行工程,進(jìn)入的也是Realize non-lazy classes(非懶加載) (for +load methods and static instances)模式,也會(huì)走到load_categories_nolock這個(gè)函數(shù),這個(gè)函 數(shù)的count是2,也就是一個(gè)一個(gè)的加載,這里的count是在processCatlist(hi->catlist(&count));(閉包)得到的,其實(shí)也就是在_getObjc2CategoryList這個(gè)地方獲取的,由Macho決定的。

結(jié)語(yǔ)

這篇文章主要介紹了分類的加載,跟iOS類的加載原理(上)一起整體介紹了分類的加載原理,如有錯(cuò)誤,煩請(qǐng)大家留言指正,相互交流學(xué)習(xí)。

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

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

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