iOS底层原理19:类和分类的加载

2021-08-09  本文已影响0人  黑白森林无间道

前面已经探究了类的加载流程,类分为懒加载类非懒加载类,他们有不同加载流程,下面来探究下分类的加载,以及分类和类搭配使用的情况

分类的本质

准备工作

main.m中定义 HTPerson的分类HT, 代码如下

image

探索分类本质的三种方法

探索分类的本质,有以下三种方式

【方式一】:通过clang

通过clang -rewrite-objc main.m -o main.cpp命令,查看编译后的 c++文件

image

搜索struct _category_t,如下所示

image image image

这里我们发现一个【问题】:分类中定义的属性没有相应的set、get方法,我们可以通过关联对象来设置(关于如何设置关联对象,我们将在下一篇中进行分析)

【方式二】:通过Xcode文档搜索 Category

通过快捷键command+shift+0,搜索Category

image

【方式三】:通过objc源码搜索 category_t

通过objc818源码搜索category_t类型

image

分类的加载的源码分析

分类的底层结构是结构体category_t,下面我们就来探究 分类是何时加载进来的,以及加载的过程

分类加载的引入

WWDC2020中关于数据结构的变化(Class data structures changes)视频地址,苹果为分类和动态添加专门分配的了一块内存rwe,因为rwe属于dirty memory,所以肯定是需要动态开辟内存。下面从class_rw_t中去查找相关rwe的源码

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
    
    // ...省略代码
    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();
        // 判断rwe是否存在
        if (fastpath(v.is<class_rw_ext_t *>())) {
            // 如果已经有rwe数据,直接返回地址指针
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            // 为rwe开辟内存并且返回地址指针
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }
    // ...省略代码
}

从代码可以看出,extAllocIfNeeded方法用来开辟rwe内存,全局搜索extAllocIfNeeded,在下列几个地方有相关调用:

本文主要来探究分类的加载,👇我们来分析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);
    // 获取rwe
    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) {
                // 当mlists的个数为 64时,对方法进行排序,然后将 mlists加载到rwe中
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            // 如果 mcount = 0,mlist存放的位置在63个位置,总共是0 ~ 63, mlists最多存放64个方法列表(mlist)
            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);
}

attachCategories准备分类的数据,然后调用attachLists将数据添加到rwe中,那么到底哪些地方调用attachCategories方法,

image
image

attachToClass流程流程分析

全局搜索attachToClass,发现只有methodizeClass方法中进行了调用

image

load_categories_nolock流程分析

image image

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

从源码可以看出,attachLists方法总共有三个流程分支:
【流程1】:0 lists -> 1 list

【流程2】:1 list -> many lists

【流程3】:many lists -> many lists

image image

实例验证分类的加载

通过上面的两个例子,我们可以大致将分类 是否实现+load方法的情况分为4种

类和分类 分类实现+load 分类未实现+load
类实现+load 非懒加载类+非懒加载分类<span class="Apple-tab-span" style="white-space:pre"></span> 非懒加载类+懒加载分类<span class="Apple-tab-span" style="white-space:pre"></span>
类未实现+load 懒加载类+非懒加载分类<span class="Apple-tab-span" style="white-space:pre"></span> 懒加载类+懒加载分类

准备工作

非懒加载类和非懒加载分类的加载

主类实现了+load方法,分类同样实现了+load方法,在前文分类的加载时机时,我们已经分析过这种情况,所以可以直接得出结论,这种情况下

运行代码,发现会调用attachCategories方法,来加载分类信息,通过bt查看函数调用栈

image

在相应函数出设置断点,打印结果如下


image image

非懒加载类与懒加载分类

主类实现了+load方法,分类未实现+load方法

image image image

从上面的打印输出可以看出,分类的方法和类的方法已经合并到一起了,方法的顺序是 HTA分类-HTPerson类,此时分类已经 加载进来了,但是还没有排序,说明这种情况下分类数据在编译时就与类数据合并到一起了,不需要运行时添加进去

image

懒加载类与懒加载分类

主类和分类均未实现+load方法

image

其中realizeClassMaybeSwiftMaybeRelock是消息流程中慢速查找中的函数,即在第一次调用消息时才会去加载懒加载类

image image

【结论】:

懒加载类与非懒加载分类

主类未实现+load方法,分类实现了+load方法

image image image

结论:

多分类的情况

新增两个分类,HTPerson (HTB)HTPerson (HTC)

image

通过不同组合来,验证类和分类的加载,总结如下

实现+load方法的分类个数 非懒加载类 懒加载类
0 编译时类数据与分类数据已合并 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:0 </br> __objc_nlcatlist:0 首次接收消息时,才加载类数据,分类数据与类数据,在编译时已合并到一起 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:0 </br> __objc_catlist:0 </br> __objc_nlcatlist:0
1 程序启动加载类数据,load_images时加载分类数据 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:3 </br> __objc_nlcatlist:1 程序启动加载类数据(编译器将类标记为非懒加载类),分类数据与类数据,在编译时已合并到一起 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:0 </br> __objc_nlcatlist:0
2 程序启动加载类数据,load_images时加载分类数据 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:3 </br> __objc_nlcatlist:2 编译后,类仍是懒加载类,程序启动(load_images方法中)会加载类和分类数据 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:0 </br> __objc_catlist:3 </br> __objc_nlcatlist:2
3 程序启动加载类数据,load_images时加载分类数据 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:1 </br> __objc_catlist:3 </br> __objc_nlcatlist:3 编译后,类仍是懒加载类,程序启动(load_images方法中)会加载类和分类数据 </br> MachO文件中类和分类数据如下:</br> __objc_classlist:1 </br> __objc_nlclslist:0 </br> __objc_catlist:3 </br> __objc_nlcatlist:3
image image

程序启动加载类数据,load_images时按照MachO中 __objc_catlist中的顺序挨个加载分类数据

程序启动加载类数据,load_images时按照MachO中 __objc_catlist中的顺序挨个加载分类数据

首次接收消息时,才加载类数据,分类数据与类数据,在编译时已合并到一起

image

程序启动加载类数据(编译器将类标记为非懒加载类),分类数据与类数据,在编译时已合并到一起

image

编译后,类仍是懒加载类,程序启动(load_images方法中)会加载类和分类数据,类和分类的加载流程:load_images --> prepare_load_methods --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories

image

编译后,类仍是懒加载类,程序启动(load_images方法中)会加载类和分类数据,类和分类的加载流程:load_images --> prepare_load_methods --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories

image
上一篇 下一篇

猜你喜欢

热点阅读