类加载原理(下)

1.jpeg

在上一篇文章类加载原理(中)我们探索了非懒加载类的加载原理、懒加载类的加载原理以及分类的一些前期探索。这篇文章我们继续看看分类的加载和实现。

分类探索前言

在上一篇文章我们在methodizeClass方法里,进入prepareMethodLists方法处理方法后发现并不会走if (rwe) rwe->methods.attachLists(&list, 1);因为这个时候rweNULL。那我们就要去查看下这个rwe是什么情况下被赋值呢?我们直接command+点击跳转查看发现就在本方法的开头里赋值。

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();
    //省略下面代码
}

从这里我们发现是从rw->ext()获取值的,那我们就进一步跟踪ext()去看看

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

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

我们从上面的代码发现是个class_rw_ext_t类型,并且发现了他的内存开辟方法extAllocIfNeeded(就在class_rw_ext_t *ext() const下方)。并且在这个方法里直接去利用get_ro_or_rwe方法就获取到了rwe,如果不行就直接调用extAlloc方法alloc一个。那我们继续跟踪下extAllocIfNeeded这个方法,看在哪儿调用了。我们全局搜索:

2.png

经过查看图中搜索到的地方,排除第一个上面的方法实现。其他的分别在:

attachCategories : 添加分类方法
objc_class::demangledNamedemangledName方法在处理future类的时候
class_setVersion : 类版本设置方法
addMethods_finish :添加方法结束
class_addProtocol : 类添加协议方法
_class_addProperty : 类添加属性方法
objc_duplicateClass : 处理重复类方法

到这里我们发现太多方法了,那我们继续看methodizeClass方法下面的代码看看能不能找到一些线索,到最后我们看到attachToClass方法,跟踪进去发现最后也是调用了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);
   //省略下面代码
}

在这个方法里有三处调用,前两处收到变量previously的影响,而这个变量又是从方法里的参数直接传进来的。所以我们去搜索methodizeClass方法看看在哪里有传这个previously参数。发现是在方法realizeClassWithoutSwift里直接传进来的,而这个参数又是从realizeClassWithoutSwift方法作为参数传进来的,所以我们继续反向查找溯源realizeClassWithoutSwift。搜索结果我们发现realizeClassWithoutSwift方法调用处previously参数都是传的nil。所以我们基本可以判定前面if不会走。我们只需要看最后一个objc::unattachedCategories.attachToClass方法调用处。

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

        
        /* *********************尝试打印看看我们自己的类能不能进来*****************/
        bool isMeta = (flags & ATTACH_METACLASS);
        const char *mangledName = cls->nonlazyMangledName();
        //定义自己的类名
        const char *ZYPersonName = "ZYPerson";
        //比较自己的类名和读取的是否一致一致就进入if
        if (strcasecmp(ZYPersonName, mangledName) == 0) {
            if (!isMeta) {
                printf("ZY 我们需要的信息: %s - - %s\n",__func__,mangledName);
            }
        }
        /* *********************尝试打印看看我们自己的类能不能进来*****************/
        
        
        
        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);
        }
    }

分析:从上面这些方法发现都是在运行时才调用的方法,以及一些注释我们还发现很多都有用到* fixme来描述,也就是说这个rwe的创建是放在运行时进行动态处理的,这样符合了WWDC2020里关于类的变动和分析的说法。从上面的方法分析我们可以知道只有添加分类最符合我们的要求,因为从WWDC2020视频里讲的在添加分类、动态添加方法等地方会有这个rwe,并且我们在methodizeClass方法最后也是引导我们走了attachToClass并且在这个方法最终是调用方法attachCategories,而分类我们从来没有探索过,也不知道它在什么时候加载处理的,所以我们去追踪分类这条线,

类和分类搭配加载

上面我们分析了attachCategories这个方法的一些内容处理,下面我们来探索下attachCategories加载的流程。我们仍然全局搜索attachCategories看看它在什么时候被调用:

3.png

从上面的全局搜索我们可以确定调用的地方有两个:

attachToClass : 添加分类到类
load_categories_nolock : 加载分类

前期分析和预备工作

到这里我们不禁要思考我们利用这种方法调用追溯法是否合适,因为发现我们这种方法查找到的路有多条,而且我们不知道是否后面还有多分支调用上面这两个方法。如果用这种方法查找追溯会变得特别复杂,最理想的情况是跟踪一个方法一直只有一条线路。但现在显然不行,所以我们换一种方法 我们直接去上面两个方法和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);

        /* *********************尝试打印看看我们自己的类能不能进来*****************/
        bool isMeta = (flags & ATTACH_METACLASS);
        const char *mangledName = cls->nonlazyMangledName();
        //定义自己的类名
        const char *ZYPersonName = "ZYPerson";
        //比较自己的类名和读取的是否一致一致就进入if
        if (strcasecmp(ZYPersonName, mangledName) == 0) {
            if (!isMeta) {
                printf("ZY 我们需要的信息: %s - - %s\n",__func__,mangledName);
            }
        }
        /* *********************尝试打印看看我们自己的类能不能进来*****************/
     
        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};

            /* *********************尝试打印看看我们自己的类能不能进来*****************/
            const char *mangledName = cls->nonlazyMangledName();
            //定义自己的类名
            const char *ZYPersonName = "ZYPerson";
            //比较自己的类名和读取的是否一致一致就进入if
            if (strcasecmp(ZYPersonName, mangledName) == 0) {
                printf("ZY 我们需要的信息:load_categories_nolock %s - - %s\n",__func__,mangledName);
            }
            /* *********************尝试打印看看我们自己的类能不能进来*****************/
            
            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();

    
    /* *********************尝试打印看看我们自己的类能不能进来*****************/
    const char *mangledName = cls->nonlazyMangledName();
    //定义自己的类名
    const char *ZYPersonName = "ZYPerson";
    //比较自己的类名和读取的是否一致一致就进入if
    if (strcasecmp(ZYPersonName, mangledName) == 0) {
        if (!isMeta) {
            printf("attachCategories - ZY 我们需要跟踪的信息: %s - - %s\n",__func__,mangledName);
        }
    }
    /* *********************尝试打印看看我们自己的类能不能进来*****************/
  
 //省略下面代码
}

然后我们结合前面文章类的加载步骤我们都在特殊打印的地方添加断点,比如以下方法:
realizeClassWithoutSwift
methodizeClass
load_categories_nolock
attachCategories

然后创建一个类和一个分类来测试:

我们创建一个ZYPerson的分类ZYPerson (ZYA)。因为我们在前面探究类的加载的时候发现添加load{}方法会在类发消息前就加载了,所以我们为了保证分类加载进行我们也给分类添加上load{}。然后在main.m文件里调用分类ZYPerson (ZYA)saySomething方法。
ZYPerson .h

#import 

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

而我们在前面的打断点方法里打印查找ro 或者class信息是否包含了分类的方法。如下图:

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

类有load、分类也有load时,分类在load_categories_nolock方法里才会加载到ro里。

我们在attachCategories方法断点的地方利用lldb调试bt命令查看堆栈的方法查看什么时候通过什么路径调用了我们的attachCategories方法。

_read_images非懒加载类打印;
realizeClassWithoutSwift方法打印;
methodizeClass方法打印;
以及上面我们分析的attachToClass
load_categories_nolock方法打印。
最后我们到attachCategories打印里去bt打印堆栈信息。 bt结果如下:

4.png

从上面的打印来和bt结果来看上面我们这种类和分类的搭配走的方法流程是:_read_images 非懒加载类->realizeClassWithoutSwift->methodizeClass-> attachToClass
在这个地方转折调用了dyld 然后走
load_images->loadAllCategories->load_categories_nolock->attachCategories

2,类无load 、分类有load

我们把ZYPerson类里面的load方法屏蔽保留分类ZYPerson(ZYA)里的load。其他环境不变。然后运行我们的代码。结果如下:

12.png

类无load、分类有load时,分类中的方法在编译阶段已添加到ro

5.png

类无load,分类有load。不会走到attachCategories方法只会走类非懒加载流程:(也许这就是被迫营业了吧)
_read_images 非懒加载类->realizeClassWithoutSwift->methodizeClass-> attachToClass

3,类有load 、分类无load

我们把分类ZYPerson(ZYA)里面的load方法屏蔽保留类ZYPerson里的load。其他环境不变。然后运行我们的代码。结果如下

13.png

类有load、分类无load时,分类中的方法在编译阶段已添加到ro

6.png

类有load,分类无load。不会走到attachCategories方法只会走类非懒加载流程:
_read_images 非懒加载类->realizeClassWithoutSwift->methodizeClass-> attachToClass

4,类无load 、分类无load

我们把分类ZYPerson(ZYA)和类ZYPerson里的load都屏蔽。其他环境不变。然后运行我们的代码。结果如下

14.png

这次我们发现是先走到了main.m文件的main函数里断点到了ZYPerson类的alloc方法,在这之前什么打印都没有,然后我让断点往下走一步就出现了上图的步骤先到realizeClassWithoutSwift打印ro没有发现分类方法,再往下走一步断点到了methodizeClass方法打印ro发现了分类方法已经添加进去了。

类无load、分类无load时,分类中的方法在类第一次发送消息后会重新走类加载流程到methodizeClass方法才会加载进ro

7.png

类有load,分类无load。不会走到attachCategories方法。在类没有发消息前我们打印的流程方法一个都不走。但是如果让它alloc成功也就是发送第一次消息后就直接走了非懒加载类流程了。

多个分类的情况,如我们创建三个ZYPerson 的分类ZYPerson(ZYA)ZYPerson(ZYB)、``ZYPerson(ZYC)。

因为分类太多这里就不把具体的没种情况分析过程列出来了,我直接贴结果:

1,类有load,多个分类都有load:

类有load和多个分类都有load,调用attachCategories方法初始化分类。

2,类有load,多个分类都无load

分类中的方法在编译阶段已添加到data()中,不会调用attachCategories方法。

3,类有load,多个分类部分实现load方法

类有load和多个分类部分实现load方法,调用attachCategories方法初始化分类。

4,类无 load,多个分类都无load:

类无 load,多个分类都无load,第一次消息发送时初始化,并且分类中的方法自动添加到data()中。

5,类无 load,多个分类部分实现load方法:

类无 load,多个分类超过一个实现load,会走attachCategories方法初始化分类。如果只是一个分类实现load,那么分类中的方法在编译阶段已添加到data()中,不会调用attachCategories方法。

分析attachCategories方法:

上面我们探索了attachCategories的调用流程和条件。下面我们查看下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);
}

从上面的代码分析我们可以把这个方法分为三个部分。

第一部分:前期预备工作
    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();

这里创建了三个数组,分别是方法数组mlists属性数组proplists协议数组protolists,并且每个数组的大小都是64 ATTACH_BUFSIZ = 64;
然后就去根据传入的参数flags 判断是否是元类。并且去获取rwe

rwe的创建处理:
auto rwe = cls->data()->extAllocIfNeeded();

以上代码我们进行以下跟踪(文章开头有跟踪但是并不完全),看看文章开头一直在已获得rwe到底是什么时候存在的。command+点击跟踪extAllocIfNeeded()

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

也就是在这里,开始创建rwe,并且第一次调用了attachLists方法,进行方法处理,这个方法在下面的步骤是重头戏

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

这部分主要是针对方法、属性、协议三个数组进行获取值和响应处理。这里我们只看方法数组的处理。

在这里我们看到有区分元类和非元类的处理:

读取方法数组:

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

methodsForMeta:

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

如果是元类就返回类方法,如果非元类就返回实例方法。

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

我们在这个for循环前开启我们之前添加的特殊打印方法,确保我们的ZYPerson(ZYA)分类进来后我们进行精准分析。我们在打印代码打上断点,使流程进入我们的方法处理for循环,然后让for循环跑一次,这个时候我们看看处理的第一个方法,是怎么添加到数组的。如图

15.png
16.png
17.png

其实在这里如果我们有多个分类比如上面分析的三个分类的时候可以在这里打印全部的分类信息,然后查看添加数组情况,会更直观,我在这里没有截图,大家可以自己去打印试试。

mlist是个数组指针,里面存的是分类的方法,而mlists是个数组二维指针,他把mlist倒叙插入到数组第63位(最后位)。

第三部分:处理前面添加的方法数组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);

分析:这部分因为mcount在前面自动mcount ++了,所以此处大于0,进入if条件。第一行代码prepareMethodLists就是方法数组排序。(mlists + ATTACH_BUFSIZ - mcount 这个参数就是利用的地址平移操作)
然后就对上面获取/创建(前面有探究如果存在就获取不存在就创建)的rwe进行调用方法attachLists添加mlists操作。

是主要流程都是去调用了一个attachLists方法。那我们就去看看这个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 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();
        }
    }

我们在上述方法断点跟踪打印:

第一步:创建数组array(),并且将array()从一维数组变成二维指针数组
18.png

从上图我们可知,第一次进入该方法直接进入eles,进行array()初始化和设置大小,然后再里面存放本类方法列表数组指针在末位置。

19.png

最后for循环添加分类方法列表数组指针到数组最前面。所以array()里存的的都是指针,然而这些指针又是指向一个个method_list_t

第二步:将上一步的array()进行再次处理,从二维指针数组变成多维指针数组。如图
20.png
第三步:第一次分类创建rw的时候就会进入到这里,对本类的方法进行处理,进入点在attachCategories方法的auto rwe = cls->data()->extAllocIfNeeded();
21.png

补充

解析rorwrwe

我们从WWDC2020关于类的介绍视频中,我们可以知道:
ro:是指clean memory,是直接从磁盘获取的,权限是只读的,所以不会被外界的操作影响。

rw:是指dirty memory,是从ro复制一份过来的,权限是可读可写的,在运行时过程中可以通过添加分类、调用运行时添加方法等方式来动态添加方法、“属性"等内容。rw存在的意义是为了适应运行时的功能使得我们开发过程中动态添加的东西可以被使用。但是我们不能一直在这个里面添加,因为视频里说这部分是比较"昂贵"的,因为手机升级内存费用是非常高的,但是mac 却可以使用,因为mac本身支持的内存大小就要比手机大。为了解决这个无限增大rw内存的问题,引出了下面的rwe。因为我们最理想的状态是我们使用过程中对rw使用最好能像ro一样,需要的东西直接获取就好,不会被添加和的新东西污染。所以说clean memory越多越好,在这样的一个希望中就诞生了rwe

rwerw的拓展,也被标志为ext。目的是为了对那些可以在动态添加的内容上标记、管理。减少这些在运行时可变的东西污染到rw的数据。做了一个隔离的工作。

至此,文章告一段落,原创码字不易,如能给您带来些许启发那也是给作者的极大鼓励。也盼望需要转载的朋友请标注出处,谢谢!

遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。

你可能感兴趣的:(类加载原理(下))