iOS底层探索之类的加载(三): attachCategories分析

1.回顾

在上篇博客中,已经对类的加载有了一定的了解,分类的加载也定位到了attachCategories方法中,那么本篇博文将对分类进行探索分析!

iOS底层探索之类的加载(二): realizeClassWithoutSwift分析
在这里插入图片描述

2. 分类分析

2.1 分类加载路线

在上篇博客中通过反推,已经确定了分类加载的两条路线,分别是:

  • methodizeClass --> attachToClass --> attachCategories
  • load_images --> loadAllCategories --> load_categories_nolock --> attachCategories

分类加载路线

  • realizeClassWithoutSwift

methodizeClass在realizeClassWithoutSwift 中调用:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;
。。。。。。。。省略。。。。。。
 // Attach categories
    methodizeClass(cls, previously);

    return cls;
}
  • 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();
。。。。。。。。省略。。。。。。    
 // 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);
。。。。。。。。省略。。。。。。

}

通过搜索realizeClassWithoutSwift的调用,发现previously这个参数是nil,也就是methodizeClass方法的参数previously也是等于nil,也就是如下图中,代码if不会执行

Attach categories

那么为什么previouslynil呢?那这个参数的意义是什么呢?

应该是苹果工程师便于调式代码使用的,也就是备用的参数,便于动态化进行调式。

2.2 attachCategories

通过注释可以知道大概意思是:将方法列表、属性和协议从分类附加到类中。假设cats中的categories全部加载完毕,并按加载顺序排序好了,首先是最旧的类别最先开始附加到类中。

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

  1. 首先是创建了三个数组,用来处理方法、属性和协议,最大 存储空间是64
   constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];
  1. 然后是判断是否来自fromBundle,是否是元类,进行标记,对rwe进行初始化。
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
  1. 通过for循环对分类的方法、属性、协议进行处理,将相关信息attach到类中。获取分类方法列表,如果当前类是元类则获取类方法列表,否则获取实例方法列表:
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();
        }
  • methodsForMeta
method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

对分类列表进行循环处理,在插入数据时,以倒序( mlists[ATTACH_BUFSIZ - ++mcount] = mlist)的方式插入。需要注意的是mLists是一个二维数组,测试打印结果如下:

(lldb) p mlist
(method_list_t *) $4 = 0x00007fff80b44aa8
(lldb) p mlists
(method_list_t *[64]) $5 = {
  [0] = 0x00007fff80849050
  [1] = 0x00007fff80848a50
  [2] = 0x00007fff205190b9
  [3] = 0x00007fff203acfca
  [4] = nil
  [5] = 0x0000000000001e00
  ............
  [58] = 0x00000001003660f0
  [59] = 0x0000000100366140
  [60] = 0x00007ffeefbf5b50
  [61] = 0x00000001002f27fa
  [62] = 0x00007ffeefbf5b50
  [63] = 0x00007fff80b44aa8
}
  • 从打印结果可以验证,最大存储空间是是64
  • 把本类的方法列表的地址放在了数组中的最后一个元素,对比地址是一模一样0x00007fff80b44aa8
  1. 在对方法、属性、协议的处理完之后,就会将相关的集合数据插入到rwe中:
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);

需要我们重点注意的是,存方法的mlist是一个二维数组。分类方法的排序,也只是针对各个自己分类内的方法进行分别排序,并不会将所有分类的方法都全部集中放到一个集合中进行排序。

2.3 rew处理

在上一篇博客中,已经提到了rwe的赋值是来源于rw->ext()代码如下:

auto rwe = rw->ext();

ext() 是属于class_rw_t结构体中的方法

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() {
    //获取rwe
    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 {
        //创建rwe
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}

class_rw_t中还有rwe创建和获取方法。
通过在objc源码里面跟踪extAllocIfNeeded方法,发现会调用extAlloc方法进行初始化。流程中会将ro的数据优先插入到rwe中:

  • extAlloc
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }

    // See comments in objc_duplicateClass
    // property lists and protocol lists historically
    // have not been deep-copied
    //
    // This is probably wrong and ought to be fixed some day
    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);
    }

    set_ro_or_rwe(rwe, ro);
    return rwe;
}

从上面extAlloc的方法中可以看出,各个分支语句对方法、属性、协议的处理都会调用attachLists方法,那么现在就去分析下attachLists

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

通过代码的断点,可以看出addedLists是一个指针,指向一个二维数组的指针,如下图所示:

attachLists

  1. 分类初次进入,会进行array()的初始化,同时设置数组的大小,即为原类的列表数量添加分类的列表数量。同时先将类的list放到最后一个位置,这时候一维数组变为二维。
 // 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();

通再过一个for循环把将分类对应的list添加到array()中去

  1. 下次再次进入时,由于array()已经初始化,所以会走到if分支中。
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();
        }

验证一下当前array()的数据顺序,和第一次插入时是一致的,类list在后,分类list在的前面,最后由二维还是变为了二维数组。
lldb调试验证

此时malloc会开辟一个新的newArray重新初始化,将原数组的数据,进行顺序不变的情况下,插入到新的array中,同时将新增的分类list插入到第一个位置处。

  1. 在前面rwe的创建过程中,已经进行了一维数组的创建,那么这个时候再调用attachLists时,会将ro的数据优先放入到rwe对应的一维数组中去。
else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 

3. 类和分类的加载

懒加载类与⾮懒加载类: 是指当前类是否实现load⽅法。
实现了load方法就是非懒加载的类。
建立分类调试验证结果如下:

3.1 分类和类都实现load方法

分类和类都实现load方法,就是非懒加载类和非懒加载分类情况。

  • 主类调用路径依次是:
    map_images ->_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass
    打印方法都是主类的,没有分类的,说明此时分类的还没有加载出来
    主类初始化

调用map_images方法就是对类开始初始化流程,methodizeClass会对ro中的方法进行排序,而rwe还未创建。

  • 分类:load_images->loadAllCategories->load_categories_nolock->attachCategories
    分类初始化
  • 过掉断点,再断点在attachCategories方法里面,打印分类方法如下
    分类方法

3.2 主类实现load,分类没有load

主类实现load,分类没有load,这属于非懒加载类和懒加载分类情况,通过断点调试,它没有走attachCategories,那么分类的方法时什么时候加载的呢,我们在realizeClassWithoutSwift加个断点,打印一下ro的信息。
调试信息

这个时候已经有了分类的信息,由此得出结论:这个时候分类的信息是在data()里面获取的。

方法调用路径依次是:
map_images->map_images_nolock->_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass->attachToClass

3.3 主类没有load,分类有实现load

主类没有load,分类有实现load,就是懒加载类和非懒加载分类情况。
在这里插入图片描述

控制台打印ro方法列表,可以看到分类的方法在前面,也就说,在插入方法的时候,分类的方法插在主类的前面。

在这里插入图片描述

方法调用路径依次是:
map_images->map_images_nolock->_read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass->attachToClass ,并且不会调用attachCategories方法,同理验证分类的方法也是在data()中获取的,分类中的方法在编译阶段已添加到data()中。

3.4 主类、分类都没有实现load

主类、分类都没有实现load,就是懒加载类和懒加载分类的情况。
通过验证:懒加载类在第一次消息发送的时候,加载类和分类的方法,也是从data()中获取的。
调试打印

方法调用路径依次是:lookUpImpOrForward->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift不会调用attachCategories方法,

懒加载类,第一次消息发送时初始化,并且分类中的方法自动添加到data()

3.5 有多个分类实现了load方法有的没有实现

主类load方法没有实现,但是多个分类中,有的实现了load方法,有的没有实现load方法。

  • 主类load方法没有实现,执行路径是:
    _read_images(非懒加载类)->realizeClassWithoutSwift->methodizeClass->attachToClass,分类方法从data()中获取`
  • 主类有load方法,执行路径是:
    _read_images(非懒加载类)->realizeClassWithoutSwift->load_categories_nolock->attachCategories
    分类方法从attachCategories中动态添加。

4. 总结

  • 懒加载的类非懒加载类的区别是,是否实现了+load方法
  • 类和分类的方法是在各自的list进行排序的
  • 尽量避免使用过多的load方法,会消耗太多加载时间
  • addedLists是一个指针,指向一个二维数组的指针
  • rwe并不是每个类都有,是在runtime运行时向类添加方法、分类、协议,以及设置版本时,才会对rwe进行操作
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡西Sensei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值