OC底层探索(十六): 类的加载(四)

2022-05-22  本文已影响0人  ShawnAlex

所用版本:

建议先看下
OC底层探索(十三): 类的加载(一)
OC底层探索(十四): 类的加载(二)
OC底层探索(十五): 类的加载(三)

看下分类的加载情况, 我们创建一个分类, 断点进行看一下。创建分类可参考: OC 创建分类

分类的加载

分类加载有4种情况,

针对这四种情况, 我们依次探索下分类加载链

分类, 主类都有 +load方法

跟断点走可发现, 并没有出现在_dyld_objc_notify_register(&map_images, load_images, unmap_image);第一个参数 &map_images 去处理, 而是在load_images中进行。

map链

看打印信息或者走断点也能看出map_images链先于load_images完成.

确保分类进入

当然我们也为了确保是我们创建的分类进入, 断点读取下, 也可发现类型为我们自定义分类"SRTestCategory"。 因为有+load, 当前类已经变为非懒加载类, 所以会走if (cls->isRealized())判断中的attachCategories。 关于load_categories_nolock方法

load_categories_nolock
static void load_categories_nolock(header_info *hi) {
    
    // header_info: Images in the shared cache will have an empty array here while those
    // allocated at run time will allocate a single entry.
    // 在共享缓存中的镜像, 将有一个空数组,而在运行时分配的将分配一个条目。
    
    // ...
    // header_info_rw rw_data[];
    //} header_info;

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

            //将cat和hi包装成 locstamped_category_t
            locstamped_category_t lc{cat, hi};

            // 如果当前类不存在, 忽略分类
            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;
            }

            // Process this category.
            // 处理分类
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                
                // Stub classes 存根类不会被实现.
                // 存根类在初始化之前不知道其元类,
                // 因此我们必须向存根本身添加具有类方法或属性的类别。
                // methodizeClass()将找到它们,并根据需要将它们添加到元类中。
                
                // 其中: category_t *cat = catlist[I];
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                
                // 首先,注册分类在其目标类。
                // 然后,如果类已实现,则重建该类的方法列表(etc)。
                
                // 实例
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    
                    if (cls->isRealized()) {
                        // 非懒加载类
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        // 懒加载类
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                // 类
                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    // 元类非懒加载
                    if (cls->ISA()->isRealized()) {
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        // 懒加载
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    // 处理Catlist, 实际是从mach-o中__objc_catlist, __objc_catlist2获取分类
    // GETSECT(_getObjc2CategoryList,        category_t * const,    "__objc_catlist");
    // GETSECT(_getObjc2CategoryList2,       category_t * const,    "__objc_catlist2");
    // 闭包的调用
    processCatlist(hi->catlist(&count)); 
    processCatlist(hi->catlist2(&count));
}
    // 闭包的调用
    processCatlist(hi->catlist(&count)); 
    processCatlist(hi->catlist2(&count));

类似以调用block(实际上是调用闭包), &count这个参数是传进去的。执行顺序也是先 processCatlist(hi->catlist(&count));再执行processCatlist里面的操作。emmm......话说苹果为何单写一个方法分出去 ( -_- ! )

接着看下闭包内部

关于category_t, 也可以通过Clang命令转成cpp文件查看底部实际执行

clang -rewrite-objc 分类.m
cpp

可看到包含:

分类实例方法 实例/类方法

因为我添加了3个实例方法1个类方法, 看底层也能看出分类没有元类那套概念。关于属性, 我们新增一个属性看下。

分类属性 分类属性

可看出虽然写了属性, 但是系统并不会自动创建set, get方法。所以想给分类添加属性还得靠关联对象方式。

其实load_categories_nolock主要做了

那么是怎样个规定 添加的, 继续跟断点走到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.

// 将分类方法列表/属性/协议搬到类中.
// 假设分类在cats中, 都是按加载顺序加载和排序的,旧分类优先

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{    
...

    /*
     * 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.
     
     * 只有少数分类在发布期间超过64个类别。
     * 这使用栈,避免了malloc。
     * 分类必须按从后到前的正确顺序添加。
     * 为了实现拆分,我们从前到后迭代cats_list,向后构建本地缓冲区,并调用块上的attachLists。
     * attachLists 方法预先列出了list,最终结果按预期顺序排列。
     */
    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) {

            // 判断有没有超出最大64
            // constexpr uint32_t ATTACH_BUFSIZ = 64;
            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);
       ...
    }

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

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

拿方法列表举例跟一遍流程:


mlist

验证一下

验证 验证

mlist是我们分类列表, 里面3个方法, 地址: 0x0000000100008028, 看下赋值之后mlists最后一个mlist[63]也为0x0000000100008028

image.png
extAllocIfNeeded

关于取rwe方法 auto rwe = cls->data()->extAllocIfNeeded(); 看一下

    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));
        }
    }
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();
    // 开辟内存空间, 底层还是alloc
    auto rwe = objc::zalloc<class_rw_ext_t>();
    // 设置版本 元类-7, 非元类-0
    rwe->version = (ro->flags & RO_META) ? 7 : 0;
    // 获取ro方法列表
    method_list_t *list = ro->baseMethods;
    if (list) {
        if (deepCopy) list = list->duplicate();
        // 将ro的方法列表放入rwe中。
        rwe->methods.attachLists(&list, 1);
    }

    // 获取属性
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

   // 获取协议
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
     //设置rwe, rwe->ro = ro;
    set_ro_or_rwe(rwe, ro);
    return rwe;
}
    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
    }
template<class T>
T *zalloc()
{
    return Zone<T, sizeof(T) % MALLOC_ALIGNMENT == 0>::alloc();
}

接着往下走, 走到prepareMethodLists和rwe赋值地方, 其中mlists + ATTACH_BUFSIZ - mcount实际上是内存地址平移, mlists + 64 - 1 = 首地址 + 63 平移即新添加的mslit的指针的地址(指针的指针) 进入下一个关键方法attachLists

attachLists
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        
        // 没有新增直接返回,
        // 话说addedCount = 0 无需新增, 之前直接不调用不就好了么 :)
        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;

            // 将旧的数组插入新的数组中。
            // 从留意下: i 初始: oldCount - 1 从后往前插入
            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I];
            
            // 新数组插在旧数组前面
            // 从前往后插入 addedLists
            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;
            // 如果oldList存在,array[endIndex] 最后一个元素为 oldList指针。
            if (oldList) array()->lists[addedCount] = oldList;
            // 循环将新加入的放到list前面。从前往后一个一个放。留意下这里都是指针。
            for (unsigned i = 0; i < addedCount; I++)
                array()->lists[i] = addedLists[I];
            validate();
        }
    }

留意下 addedLists 实际操作的指针, 是**这样一个结构, 看图

例子

addedLists实际插入的是list指针, 如果这个时候又有新的addedLists插入, 则有

addedLists

综上, 主类与分类都有+load 分类加载情况执行:

分类有 +load方法, 当前类无

主类有 +load方法, 分类无

这个2中情况是一样的

map_images_read_imagesrealizeClassWithoutSwift
分类已在data()中完成加载。首先, 这2种情况, 主类类都变成了非懒加载类()

验证一下


验证 打印 主类 分类

realizeClassWithoutSwift方法读取下ro (留意下要ro赋值完成), 读取其方法列表之后我们能发现, 分类在realizeClassWithoutSwift之前已经有了, 系统编译时在data()中早已完成处理 (mach-o中就已处理好 )。

主分类两者皆无

类allocobjc_alloclookupimpOrForwardrealizeClassWithoutSwiftmethodizeClass
推迟到主类alloc之后, 第一次消息发送时候进行, 数据已经在data()中(mach -o已经给处理好了)。验证一下

验证

多分类情况都有+load

举例验证一下:

4个分类
顺序
建立4个分类, 其中主类SRTestCategoryC加了 +load, 顺序最好也留意一下, 运行: realizeClassWithoutSwift
realizeClassWithoutSwift

首先在realizeClassWithoutSwiftro中读取下信息发现, 说明data()中没处理分类

load_categories_nolock load_categories_nolock

load_image链可以发现处理了, 在load_categories_nolock中打印cat信息, 可以发现有分类信息, 接下来会走attachCategories去处理添加分类。但是留意下会发现, 为什么没有+load的分类SRTestCategoryC呢? 继续运行

load_categories_nolock

可发现, 在第二次循环中才处理SRTestCategoryC相关。 就你叫夏洛啊(就你有+load), 好, 单独处理处理你。之后继续走attachCategories。有类方法的话类方法的attachCategories也走。

举例验证一下:

4个分类
顺序
建立4个分类, 其中主类SRTestCategoryA, SRTestCategoryC加了 +load, 顺序最好也留意一下, 运行: 验证 验证

跟单个加分类类似, 加载分类在load_images链, processCatlist中循环添加, 先处理所有不含+load分类, 在依次处理+load分类

验证, 读的 realizeClassWithoutSwift 中 ro

主类实现链: map_images_read_imagesload_imagesprepare_load_methodsprepare_load_methods
realizeClassWithoutSwiftmethodizeClass

懒加载添加 addForClass

跟主类实现链路类似, 最后分类走懒加载方法objc::unattachedCategories.addForClass

但是留意下, 此时主类链会在map_images_read_imagesload_imagesprepare_load_methodsprepare_load_methods
realizeClassWithoutSwiftmethodizeClass

map链路区别 map链路区别 map链路区别

读一下realizeClassWithoutSwift中的ro, 可发现只有主类相关, 没有分类

总结

分类加载链路情况:

示意图

分类存放情况:

上一篇 下一篇

猜你喜欢

热点阅读