類加載原理(下)

1.jpeg

在上一篇文章類加載原理(中)我們探索了非懶加載類的加載原理、懶加載類的加載原理以及分類的一些前期探索。這篇文章我們繼續(xù)看看分類的加載和實(shí)現(xiàn)。

分類探索前言

在上一篇文章我們?cè)?code>methodizeClass方法里,進(jìn)入prepareMethodLists方法處理方法后發(fā)現(xiàn)并不會(huì)走if (rwe) rwe->methods.attachLists(&list, 1);因?yàn)檫@個(gè)時(shí)候rweNULL。那我們就要去查看下這個(gè)rwe是什么情況下被賦值呢?我們直接command+點(diǎn)擊跳轉(zhuǎn)查看發(fā)現(xiàn)就在本方法的開(kāi)頭里賦值。

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

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();
    //省略下面代碼
}

從這里我們發(fā)現(xiàn)是從rw->ext()獲取值的,那我們就進(jìn)一步跟蹤ext()去看看

ext():
class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

我們從上面的代碼發(fā)現(xiàn)是個(gè)class_rw_ext_t類型,并且發(fā)現(xiàn)了他的內(nèi)存開(kāi)辟方法extAllocIfNeeded(就在class_rw_ext_t *ext() const下方)。并且在這個(gè)方法里直接去利用get_ro_or_rwe方法就獲取到了rwe,如果不行就直接調(diào)用extAlloc方法alloc一個(gè)。那我們繼續(xù)跟蹤下extAllocIfNeeded這個(gè)方法,看在哪兒調(diào)用了。我們?nèi)炙阉鳎?/p>

2.png

經(jīng)過(guò)查看圖中搜索到的地方,排除第一個(gè)上面的方法實(shí)現(xiàn)。其他的分別在:

attachCategories : 添加分類方法
objc_class::demangledNamedemangledName方法在處理future類的時(shí)候
class_setVersion : 類版本設(shè)置方法
addMethods_finish :添加方法結(jié)束
class_addProtocol : 類添加協(xié)議方法
_class_addProperty : 類添加屬性方法
objc_duplicateClass : 處理重復(fù)類方法

到這里我們發(fā)現(xiàn)太多方法了,那我們繼續(xù)看methodizeClass方法下面的代碼看看能不能找到一些線索,到最后我們看到attachToClass方法,跟蹤進(jìn)去發(fā)現(xiàn)最后也是調(diào)用了attachCategories:

methodizeClass
static void methodizeClass(Class cls, Class previously)
{
 //省略上面代碼
// 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);
   //省略下面代碼
}

在這個(gè)方法里有三處調(diào)用,前兩處收到變量previously的影響,而這個(gè)變量又是從方法里的參數(shù)直接傳進(jìn)來(lái)的。所以我們?nèi)ニ阉?code>methodizeClass方法看看在哪里有傳這個(gè)previously參數(shù)。發(fā)現(xiàn)是在方法realizeClassWithoutSwift里直接傳進(jìn)來(lái)的,而這個(gè)參數(shù)又是從realizeClassWithoutSwift方法作為參數(shù)傳進(jìn)來(lái)的,所以我們繼續(xù)反向查找溯源realizeClassWithoutSwift。搜索結(jié)果我們發(fā)現(xiàn)realizeClassWithoutSwift方法調(diào)用處previously參數(shù)都是傳的nil。所以我們基本可以判定前面if不會(huì)走。我們只需要看最后一個(gè)objc::unattachedCategories.attachToClass方法調(diào)用處。

attachToClass:
void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        
        /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
        bool isMeta = (flags & ATTACH_METACLASS);
        const char *mangledName = cls->nonlazyMangledName();
        //定義自己的類名
        const char *ZYPersonName = "ZYPerson";
        //比較自己的類名和讀取的是否一致一致就進(jìn)入if
        if (strcasecmp(ZYPersonName, mangledName) == 0) {
            if (!isMeta) {
                printf("ZY 我們需要的信息: %s - - %s\n",__func__,mangledName);
            }
        }
        /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
        
        
        
        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

分析:從上面這些方法發(fā)現(xiàn)都是在運(yùn)行時(shí)才調(diào)用的方法,以及一些注釋我們還發(fā)現(xiàn)很多都有用到* fixme來(lái)描述,也就是說(shuō)這個(gè)rwe的創(chuàng)建是放在運(yùn)行時(shí)進(jìn)行動(dòng)態(tài)處理的,這樣符合了WWDC2020里關(guān)于類的變動(dòng)和分析的說(shuō)法。從上面的方法分析我們可以知道只有添加分類最符合我們的要求,因?yàn)閺?code>WWDC2020視頻里講的在添加分類、動(dòng)態(tài)添加方法等地方會(huì)有這個(gè)rwe,并且我們?cè)?code>methodizeClass方法最后也是引導(dǎo)我們走了attachToClass并且在這個(gè)方法最終是調(diào)用方法attachCategories,而分類我們從來(lái)沒(méi)有探索過(guò),也不知道它在什么時(shí)候加載處理的,所以我們?nèi)プ粉櫡诸愡@條線,

類和分類搭配加載

上面我們分析了attachCategories這個(gè)方法的一些內(nèi)容處理,下面我們來(lái)探索下attachCategories加載的流程。我們?nèi)匀蝗炙阉?code>attachCategories看看它在什么時(shí)候被調(diào)用:

3.png

從上面的全局搜索我們可以確定調(diào)用的地方有兩個(gè):

attachToClass : 添加分類到類
load_categories_nolock : 加載分類

前期分析和預(yù)備工作

到這里我們不禁要思考我們利用這種方法調(diào)用追溯法是否合適,因?yàn)榘l(fā)現(xiàn)我們這種方法查找到的路有多條,而且我們不知道是否后面還有多分支調(diào)用上面這兩個(gè)方法。如果用這種方法查找追溯會(huì)變得特別復(fù)雜,最理想的情況是跟蹤一個(gè)方法一直只有一條線路。但現(xiàn)在顯然不行,所以我們換一種方法 我們直接去上面兩個(gè)方法和attachCategories里面,添加上一行特殊打印如下:

attachToClass:

void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
        bool isMeta = (flags & ATTACH_METACLASS);
        const char *mangledName = cls->nonlazyMangledName();
        //定義自己的類名
        const char *ZYPersonName = "ZYPerson";
        //比較自己的類名和讀取的是否一致一致就進(jìn)入if
        if (strcasecmp(ZYPersonName, mangledName) == 0) {
            if (!isMeta) {
                printf("ZY 我們需要的信息: %s - - %s\n",__func__,mangledName);
            }
        }
        /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
     
        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

load_categories_nolock:

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
            const char *mangledName = cls->nonlazyMangledName();
            //定義自己的類名
            const char *ZYPersonName = "ZYPerson";
            //比較自己的類名和讀取的是否一致一致就進(jìn)入if
            if (strcasecmp(ZYPersonName, mangledName) == 0) {
                printf("ZY 我們需要的信息:load_categories_nolock %s - - %s\n",__func__,mangledName);
            }
            /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
            
            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Ignore the category.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }
    
          //省略下方代碼
        }
    };

    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

attachCategories:

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    //省略前面代碼

    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();

    
    /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
    const char *mangledName = cls->nonlazyMangledName();
    //定義自己的類名
    const char *ZYPersonName = "ZYPerson";
    //比較自己的類名和讀取的是否一致一致就進(jìn)入if
    if (strcasecmp(ZYPersonName, mangledName) == 0) {
        if (!isMeta) {
            printf("attachCategories - ZY 我們需要跟蹤的信息: %s - - %s\n",__func__,mangledName);
        }
    }
    /* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
  
 //省略下面代碼
}

然后我們結(jié)合前面文章類的加載步驟我們都在特殊打印的地方添加斷點(diǎn),比如以下方法:
realizeClassWithoutSwift
methodizeClass
load_categories_nolock
attachCategories

然后創(chuàng)建一個(gè)類和一個(gè)分類來(lái)測(cè)試:

我們創(chuàng)建一個(gè)ZYPerson的分類ZYPerson (ZYA)。因?yàn)槲覀冊(cè)谇懊嫣骄款惖募虞d的時(shí)候發(fā)現(xiàn)添加load{}方法會(huì)在類發(fā)消息前就加載了,所以我們?yōu)榱吮WC分類加載進(jìn)行我們也給分類添加上load{}。然后在main.m文件里調(diào)用分類ZYPerson (ZYA)saySomething方法。
ZYPerson .h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZYPerson : NSObject

- (void)zyEatSugar;

+ (void)sayHappy;

- (void)zyShowTime;

@end
NS_ASSUME_NONNULL_END

ZYPerson .m

#import "ZYPerson.h"

@implementation ZYPerson
+(void)load{

}
- (void)zyEatSugar
{
    NSLog(@"%s",__func__);
}

+ (void)sayHappy
{
    NSLog(@"%s",__func__);
}

- (void)zyShowTime
{
    NSLog(@"%s",__func__);
}
@end

ZYPerson (ZYA) .h

#import "ZYPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface ZYPerson (ZYA)

- (void)saySomething;

- (void)zyA_instanceMethod1;
- (void)zyA_instanceMethod2;
+ (void)zyA_classMethod1;
+ (void)zyA_classMethod2;

@end

ZYPerson (ZYA) .m

#import "ZYPerson+ZYA.h"

@implementation ZYPerson (ZYA)

+(void)load{  }

- (void)saySomething
{
    NSLog(@"%s",__func__);
}
- (void)zyA_instanceMethod1
{
    NSLog(@"%s",__func__);
}
- (void)zyA_instanceMethod2
{
    NSLog(@"%s",__func__);
}

+ (void)zyA_classMethod1
{
    NSLog(@"%s",__func__);
}
+ (void)zyA_classMethod2
{
    NSLog(@"%s",__func__);
}
@end

main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ZYPerson *person = [ZYPerson alloc];
    
        [person saySomething];

    }
    return 0;
}
1,類有load 、分類有load

而我們?cè)谇懊娴拇驍帱c(diǎn)方法里打印查找ro 或者class信息是否包含了分類的方法。如下圖:

8.png
9.png
10.png
11.png

類有load、分類也有load時(shí),分類在load_categories_nolock方法里才會(huì)加載到ro里。

我們?cè)?code>attachCategories方法斷點(diǎn)的地方利用lldb調(diào)試bt命令查看堆棧的方法查看什么時(shí)候通過(guò)什么路徑調(diào)用了我們的attachCategories方法。

_read_images非懶加載類打??;
realizeClassWithoutSwift方法打??;
methodizeClass方法打?。?br> 以及上面我們分析的attachToClass;
load_categories_nolock方法打印。
最后我們到attachCategories打印里去bt打印堆棧信息。 bt結(jié)果如下:

4.png

從上面的打印來(lái)和bt結(jié)果來(lái)看上面我們這種類和分類的搭配走的方法流程是:_read_images 非懶加載類->realizeClassWithoutSwift->methodizeClass-> attachToClass
在這個(gè)地方轉(zhuǎn)折調(diào)用了dyld 然后走
load_images->loadAllCategories->load_categories_nolock->attachCategories

2,類無(wú)load 、分類有load

我們把ZYPerson類里面的load方法屏蔽保留分類ZYPerson(ZYA)里的load。其他環(huán)境不變。然后運(yùn)行我們的代碼。結(jié)果如下:

12.png

類無(wú)load、分類有load時(shí),分類中的方法在編譯階段已添加到ro

5.png

類無(wú)load,分類有load。不會(huì)走到attachCategories方法只會(huì)走類非懶加載流程:(也許這就是被迫營(yíng)業(yè)了吧)
_read_images 非懶加載類->realizeClassWithoutSwift->methodizeClass-> attachToClass

3,類有load 、分類無(wú)load

我們把分類ZYPerson(ZYA)里面的load方法屏蔽保留類ZYPerson里的load。其他環(huán)境不變。然后運(yùn)行我們的代碼。結(jié)果如下

13.png

類有load、分類無(wú)load時(shí),分類中的方法在編譯階段已添加到ro

6.png

類有load,分類無(wú)load。不會(huì)走到attachCategories方法只會(huì)走類非懶加載流程:
_read_images 非懶加載類->realizeClassWithoutSwift->methodizeClass-> attachToClass

4,類無(wú)load 、分類無(wú)load

我們把分類ZYPerson(ZYA)和類ZYPerson里的load都屏蔽。其他環(huán)境不變。然后運(yùn)行我們的代碼。結(jié)果如下

14.png

這次我們發(fā)現(xiàn)是先走到了main.m文件的main函數(shù)里斷點(diǎn)到了ZYPerson類的alloc方法,在這之前什么打印都沒(méi)有,然后我讓斷點(diǎn)往下走一步就出現(xiàn)了上圖的步驟先到realizeClassWithoutSwift打印ro沒(méi)有發(fā)現(xiàn)分類方法,再往下走一步斷點(diǎn)到了methodizeClass方法打印ro發(fā)現(xiàn)了分類方法已經(jīng)添加進(jìn)去了。

類無(wú)load、分類無(wú)load時(shí),分類中的方法在類第一次發(fā)送消息后會(huì)重新走類加載流程到methodizeClass方法才會(huì)加載進(jìn)ro

7.png

類有load,分類無(wú)load。不會(huì)走到attachCategories方法。在類沒(méi)有發(fā)消息前我們打印的流程方法一個(gè)都不走。但是如果讓它alloc成功也就是發(fā)送第一次消息后就直接走了非懶加載類流程了。

多個(gè)分類的情況,如我們創(chuàng)建三個(gè)ZYPerson 的分類ZYPerson(ZYA)、ZYPerson(ZYB)、``ZYPerson(ZYC)。

因?yàn)榉诸愄噙@里就不把具體的沒(méi)種情況分析過(guò)程列出來(lái)了,我直接貼結(jié)果:

1,類有load,多個(gè)分類都有load:

類有load和多個(gè)分類都有load,調(diào)用attachCategories方法初始化分類。

2,類有load,多個(gè)分類都無(wú)load

分類中的方法在編譯階段已添加到data()中,不會(huì)調(diào)用attachCategories方法。

3,類有load,多個(gè)分類部分實(shí)現(xiàn)load方法

類有load和多個(gè)分類部分實(shí)現(xiàn)load方法,調(diào)用attachCategories方法初始化分類。

4,類無(wú) load,多個(gè)分類都無(wú)load:

類無(wú) load,多個(gè)分類都無(wú)load,第一次消息發(fā)送時(shí)初始化,并且分類中的方法自動(dòng)添加到data()中。

5,類無(wú) load,多個(gè)分類部分實(shí)現(xiàn)load方法:

類無(wú) load,多個(gè)分類超過(guò)一個(gè)實(shí)現(xiàn)load,會(huì)走attachCategories方法初始化分類。如果只是一個(gè)分類實(shí)現(xiàn)load,那么分類中的方法在編譯階段已添加到data()中,不會(huì)調(diào)用attachCategories方法。

分析attachCategories方法:

上面我們探索了attachCategories的調(diào)用流程和條件。下面我們查看下attachCategories到底做了什么操作。

attachCategories分析 :

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
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();

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

從上面的代碼分析我們可以把這個(gè)方法分為三個(gè)部分。

第一部分:前期預(yù)備工作
    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();

這里創(chuàng)建了三個(gè)數(shù)組,分別是方法數(shù)組mlists屬性數(shù)組proplists、協(xié)議數(shù)組protolists,并且每個(gè)數(shù)組的大小都是64 ATTACH_BUFSIZ = 64;
然后就去根據(jù)傳入的參數(shù)flags 判斷是否是元類。并且去獲取rwe。

rwe的創(chuàng)建處理:
auto rwe = cls->data()->extAllocIfNeeded();

以上代碼我們進(jìn)行以下跟蹤(文章開(kāi)頭有跟蹤但是并不完全),看看文章開(kāi)頭一直在已獲得rwe到底是什么時(shí)候存在的。command+點(diǎn)擊跟蹤extAllocIfNeeded()

22.png
23.png
24.png
25.png

也就是在這里,開(kāi)始創(chuàng)建rwe,并且第一次調(diào)用了attachLists方法,進(jìn)行方法處理,這個(gè)方法在下面的步驟是重頭戲

第二部分:
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;
        }
    }

這部分主要是針對(duì)方法、屬性、協(xié)議三個(gè)數(shù)組進(jìn)行獲取值和響應(yīng)處理。這里我們只看方法數(shù)組的處理。

在這里我們看到有區(qū)分元類和非元類的處理:

讀取方法數(shù)組:

method_list_t *mlist = entry.cat->methodsForMeta(isMeta);

methodsForMeta:

method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

如果是元類就返回類方法,如果非元類就返回實(shí)例方法。

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

我們?cè)谶@個(gè)for循環(huán)前開(kāi)啟我們之前添加的特殊打印方法,確保我們的ZYPerson(ZYA)分類進(jìn)來(lái)后我們進(jìn)行精準(zhǔn)分析。我們?cè)诖蛴〈a打上斷點(diǎn),使流程進(jìn)入我們的方法處理for循環(huán),然后讓for循環(huán)跑一次,這個(gè)時(shí)候我們看看處理的第一個(gè)方法,是怎么添加到數(shù)組的。如圖

15.png
16.png
17.png

其實(shí)在這里如果我們有多個(gè)分類比如上面分析的三個(gè)分類的時(shí)候可以在這里打印全部的分類信息,然后查看添加數(shù)組情況,會(huì)更直觀,我在這里沒(méi)有截圖,大家可以自己去打印試試。

mlist是個(gè)數(shù)組指針,里面存的是分類的方法,而mlists是個(gè)數(shù)組二維指針,他把mlist倒敘插入到數(shù)組第63位(最后位)。

第三部分:處理前面添加的方法數(shù)組mlists
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);

分析:這部分因?yàn)閙count在前面自動(dòng)mcount ++了,所以此處大于0,進(jìn)入if條件。第一行代碼prepareMethodLists就是方法數(shù)組排序。(mlists + ATTACH_BUFSIZ - mcount 這個(gè)參數(shù)就是利用的地址平移操作)
然后就對(duì)上面獲取/創(chuàng)建(前面有探究如果存在就獲取不存在就創(chuàng)建)的rwe進(jìn)行調(diào)用方法attachLists添加mlists操作。

是主要流程都是去調(diào)用了一個(gè)attachLists方法。那我們就去看看這個(gè)attachLists方法到底做了什么

attachLists分析:

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

我們?cè)谏鲜龇椒〝帱c(diǎn)跟蹤打?。?/p>

第一步:創(chuàng)建數(shù)組array(),并且將array()從一維數(shù)組變成二維指針數(shù)組
18.png

從上圖我們可知,第一次進(jìn)入該方法直接進(jìn)入eles,進(jìn)行array()初始化和設(shè)置大小,然后再里面存放本類方法列表數(shù)組指針在末位置。

19.png

最后for循環(huán)添加分類方法列表數(shù)組指針到數(shù)組最前面。所以array()里存的的都是指針,然而這些指針又是指向一個(gè)個(gè)method_list_t

第二步:將上一步的array()進(jìn)行再次處理,從二維指針數(shù)組變成多維指針數(shù)組。如圖
20.png
第三步:第一次分類創(chuàng)建rw的時(shí)候就會(huì)進(jìn)入到這里,對(duì)本類的方法進(jìn)行處理,進(jìn)入點(diǎn)在attachCategories方法的auto rwe = cls->data()->extAllocIfNeeded();
21.png

補(bǔ)充

解析ro、rwrwe?

我們從WWDC2020關(guān)于類的介紹視頻中,我們可以知道:
ro:是指clean memory,是直接從磁盤(pán)獲取的,權(quán)限是只讀的,所以不會(huì)被外界的操作影響。

rw:是指dirty memory,是從ro復(fù)制一份過(guò)來(lái)的,權(quán)限是可讀可寫(xiě)的,在運(yùn)行時(shí)過(guò)程中可以通過(guò)添加分類、調(diào)用運(yùn)行時(shí)添加方法等方式來(lái)動(dòng)態(tài)添加方法、“屬性"等內(nèi)容。rw存在的意義是為了適應(yīng)運(yùn)行時(shí)的功能使得我們開(kāi)發(fā)過(guò)程中動(dòng)態(tài)添加的東西可以被使用。但是我們不能一直在這個(gè)里面添加,因?yàn)橐曨l里說(shuō)這部分是比較"昂貴"的,因?yàn)槭謾C(jī)升級(jí)內(nèi)存費(fèi)用是非常高的,但是mac 卻可以使用,因?yàn)?code>mac本身支持的內(nèi)存大小就要比手機(jī)大。為了解決這個(gè)無(wú)限增大rw內(nèi)存的問(wèn)題,引出了下面的rwe。因?yàn)槲覀冏罾硐氲臓顟B(tài)是我們使用過(guò)程中對(duì)rw使用最好能像ro一樣,需要的東西直接獲取就好,不會(huì)被添加和的新東西污染。所以說(shuō)clean memory越多越好,在這樣的一個(gè)希望中就誕生了rwe。

rwerw的拓展,也被標(biāo)志為ext。目的是為了對(duì)那些可以在動(dòng)態(tài)添加的內(nèi)容上標(biāo)記、管理。減少這些在運(yùn)行時(shí)可變的東西污染到rw的數(shù)據(jù)。做了一個(gè)隔離的工作。

至此,文章告一段落,原創(chuàng)碼字不易,如能給您帶來(lái)些許啟發(fā)那也是給作者的極大鼓勵(lì)。也盼望需要轉(zhuǎn)載的朋友請(qǐng)標(biāo)注出處,謝謝!

遇事不決,可問(wèn)春風(fēng)。站在巨人的肩膀上學(xué)習(xí),如有疏忽或者錯(cuò)誤的地方還請(qǐng)多多指教。

最后編輯于
?著作權(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)容