前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS底层探索——分类的加载分析

iOS底层探索——分类的加载分析

作者头像
CC老师
发布2022-01-14 14:42:51
3380
发布2022-01-14 14:42:51
举报

在上篇文章类的加载分析中,分析了非懒加载类的加载流程,ro、rw、rwe的逻辑,方法的排序流程等,本篇将重点分析懒加载类和分类的加载过程。

引入问题

首先,回顾一下方法methodizeClass,上一篇文章也做了分析。见下图:

  • 该方法是将ro中的方法、协议、属性附加到rwe中,但是跟踪代码会发现此时的rwe还是空,那么rwe何时创建呢?这是我们要摸清的!
  • 同时通过全局搜索methodizeClass方法,previously的传入值都是nil,可以理解if(previously)中的流程应该是为未来的一些处理做的预留。

进入attachToClass方法,找到了分类处理的一个入口:attachCategories方法。见下图:

通过上一篇文章分析,很遗憾,跟踪发现attachCategories没有被调用到!

最终我们通过反向推导的方式找到调用attachCategories方法的地方,也就是load_categories_nolock方法。

但是从上一篇文章的分析结果发现,load_categories_nolock方法貌似也只是在处理实现了load方法的分类有效。

那么对于懒加载的分类加载过程又是怎样的呢?attachCategories做了什么?

  • 汇总一下,我们现在要解决的问题:
  • 分类的加载流程?
  • 不同情况下,方法的排序是怎么样的?
  • rwe在何时创建?
  • attachCategories做了什么?

接着上一篇文章的内容,我们对多中情况的类和分类的加载过程进行分析。

分类的加载分析

添加一个案例,创建一个类 LGPerson ,有两个实例方法,并实现了 +load() ,

同时创建一个分类 LGPerson (LG) ,有一个实例方法,也实现了 +load() 。见下图:

代码语言:javascript
复制
// LGPerson类
@implementation LGPerson

+(void)load{}

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

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

// 分类
@implementation LGPerson (LG)

+ (void)load{}

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

(滑动显示更多)

1.非懒加载类和非懒加载分类

此种情况在上一篇文章中已经分析了。这里不再分析,直接给出结果,并验证!

  • 类初始化
  • map_images->_read_images->realizeClassWithoutSwift->methodizeClass
  • 分类初始化
  • load_images->load_categories_nolock->attachCategories

验证一下,在关键位置设置断点,运行程序:

在 methodizeClass方法中过滤到了 LGPerson,同时查看运行堆栈信息,发现 LGPerson在dyld应用程序加载时,调用map_images时即开始了类的初始化流程。

在类的ro中,成功获取了LGPerson的两个实例方法,说明此时分类的方法还没有添加到类中。见下图:

methodizeClass会对ro中的方法进行排序,而rwe还未创建!

在load_categories_nolock方法中过滤到了LGPerson类,并获取了对应的分类LG。

通过堆栈信息可以发现,同样是在dyld应用程序加载时,调用load_images时开始了分类的初始化流程。见下图:

2.懒加载类和非懒加载分类

运行程序前,先思考一下,类现在是懒加载,那么read_images过程中就不会进入

realizeClassWithoutSwift了呢?

分类依然是非懒加载,那么分类的加载是否还是通过load_categories_nolock进行初始化呢?

依然采用上面的案例,去掉LGPerson类中的+load方法。

在关键位置设置断点,运行程序:

运行结果很意外,应用程序加载阶段,在

read_images中过滤到了LGPerson,并且调用了

realizeClassWithoutSwift方法。见下图:

继续运行程序,获取LGPerson对应的ro数据,打印方法列表结果如下:

方法竟然有3个,分类的方法已经被放入到了类对应的data()中,并且处在方法列表的前面。所以可以得出以下结论:

  • 懒加载类和非懒加载分类

map_images->_read_images->realizeClassWithoutSwift->methodizeClass,

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

3.非懒加载类和懒加载分类

依然采用上面的案例,添加LGPerson类中的+load方法,去掉分类中的+load方法。

在关键位置设置断点,运行程序:

和是第二种(懒加载类和非懒加载分类)情况类似,应用程序加载阶段,在read_images中过滤到了LGPerson,并且调用了realizeClassWithoutSwift方法。

获取LGPerson对应的ro数据,打印方法列表结果如下:

方法也是3个,分类的方法已经被放入到了类对应的data()中,并且处在方法列表的前面。所以可以得出以下结论:

  • 非懒加载类和懒加载分类

map_images->_read_images->realizeClassWithoutSwift->methodizeClass,

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

如果是有多个分类,并且分类都是懒加载,流程一致!

4.懒加载类和懒加载分类

去掉类和分类中的+load方法。同样在关键位置设置过滤条件,直接运行程序,没有过滤到任何内容,运行结束。此种情况何时加载呢?

回顾一下上一篇文章类的加载分析,我们已经得出结论,懒加载的类在第一次消息发送时进行初始化。那么此种情况下,分类是否也是这样呢?

修改一下代码,向LGPerson类发送一个消息,查看运行结果:

和懒加载类的情况一致,此种情况分类中的数据也自动添加到data()中。

  • 懒加载类和懒加载分类

lookUpImpOrForward->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift,

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

5.多个分类的情况补充

  1. 类非懒加载,有多个分类,都是非懒加载 非懒加载类和非懒加载分类,调用attachCategories方法初始化分类。
  2. 类非懒加载,有多个分类,都是懒加载 分类中的方法在编译阶段已添加到data()中,不会调用attachCategories方法。
  3. 类非懒加载,有多个分类,部分实现+load方法 非懒加载类和非懒加载分类,调用attachCategories方法初始化分类。
  4. 类懒加载,有多个分类,都是懒加载 懒加载的类和懒加载分类,第一次消息发送时初始化,并且分类中的方法自动添加到data()中。
  5. 类懒加载,有多个分类,部分实现+load方法
  • 这里有两种情况:
  • 只有一个分类实现了load方法: 分类中的方法在编译阶段已添加到data()中,不会调用attachCategories方法。
  • 超过一个分类实现了load方法

6.类懒加载,超过一个分类实现了load方法

针对这种情况单独分析一下,案例说明:LGPerson类为懒加载,创建三个分类LG、LG2、LG3,其中LG和LG2实现了load方法。

运行程序,发现并没有走map_images->_read_images->realizeClassWithoutSwift的流程。

而是在load_categories_nolock中过滤到了分类处理流程,见下图:

和上面有所区别的是,并未调用attachCategories方法,

而是调用了else中的objc::unattachedCategories.addForClass(lc, cls);,

说明cls->isRealized()为false,也就是说此时类并没有初始化!addForClass中做了什么呢?

简单理解是,将类和分类进行关联,放在一个BucketT中,可以理解为一个容器。

在load_categories_nolock中完成三次循环,将分类均存储到对应的BucketT中。见下图:

继续运行程序,在methodizeClass中过滤到了LGPerson见下图:

查看堆栈信息,很熟悉,正是load方法调用的关键流程,load_images->prepare_load_methods。

我们来重新回顾一下prepare_load_methods方法,见下图:

由于类是懒加载,但是分类是非懒加载,而对LGPerson的分类进行处理时,发现类还没有实现,便调用realizeClassWithoutSwift方法,对LGPerson进行初始化。

在methodizeClass方法中,对LGPerson本类的相关方法、属性、协议等进行处理,此时分类中的数据还没有附加到本类中。继续运行程序,进而调用attachToClass方法,见下图:

在attachToClass中获取类对应的BucketT,进而获取其分类列表,调用attachCategories方法。

详解attachCategories方法

1.attachCategories流程分析

创建了三个数组,分别用于处理方法、属性和协议,最大存储空间是64,见下图:

初始化rwe:

代码语言:javascript
复制
    auto rwe = cls->data()->extAllocIfNeeded();

(滑动显示更多)

对分类的方法、属性、协议进行处理,将相关信息attach到类中。见下图:

通过下面代码,获取对应的方法列表,如果当前类是元类则获取类方法列表,否则获取实例方法列表:

代码语言:javascript
复制
   entry.cat->methodsForMeta(isMeta);

   // 区分是否为元类
   method_list_t *methodsForMeta(bool isMeta) {
       if (isMeta) return classMethods;
       else return instanceMethods;
   }

(滑动显示更多)

对分类列表进行循环处理,在插入数据时,以倒序的方式插入。同时mLists是一个二维数组。见下图:

完成方法、属性、协议的整理后,将相关的集合数据插入到rwe中,见下图:

处理方法列表时,首先对方法列表进行排序,但是需要注意的是,这里的mlist是一个二维数组,而方法的排序也只是针对各个分类内的方法进行分别排序,并不会将所有方法放到一个集合中进行排序。

2.rwe创建分析

在attachCategories中对rwe进行了初始化,见下面代码:

代码语言:javascript
复制
auto rwe = cls->data()->extAllocIfNeeded();

(滑动显示更多)

这里我们思考两个问题,

  • 什么情况下才会对rwe进行创建?
  • 在rwe创建过程中,做了哪些操作呢?

首先全局搜索extAllocIfNeeded,看哪些地方调用了rwe创建流程。

通过全局搜索,发现在向类添加方法、分类、协议,以及设置版本时,才会对rwe进行初始化。如添加协议:

也就是说除了ro数据外,如需要向类添加额外信息时才会进行rwe的创建。这和我们在类的加载分析中的分析时一致的。

在创建rwe时,内部做了什么操作呢?跟踪extAllocIfNeeded源码,其会调用extAlloc方法进行初始化。流程中会将ro的数据优先插入到rwe中,见下图:

3.attachLists分析

查看attachLists源码,addedLists是一个指针,指向一个二维数组。针对不同的情形,设置了不同的处理分支,见下图:

一维数组变二维数组

分类初次进入,会进行array()的初始化,同时设置数组的大小,即为原类的列表数量添加分类的列表数量。同时先将类的list放到最后一个位置,见下图:

再开启循环,将分类对应的list添加到array()中,见下图:

二维数组变二维数组

再次进入时,由于array()已经初始化,所以会走到下图中的分支中,验证一下当前array()的数据顺序,和第一次插入时是一致的,类list在后,分类list在前,见下图:

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

一维数组的创建

一维数组的分支何时进入呢?其实在前面rwe的创建过程中,已经进行了一维数组的创建。上面已经分析rwe的创建流程:

  • extAllocIfNeeded->extAlloc->attachLists。

在调用attachLists时,会将ro的数据优先放入到rwe对应的一维数组中,见下图:

总结

  1. 通过上面的分析,我们可以发现分类的初始化过程还是比较复杂的,所以在平时的开发过程中尽量不要实现分类的load方法,以节省性能。
  2. 对于方法的排序,并不会将类和分类的方法放在一起排序,在进行初始化过程中,只是针对各自的list中的方法进行排序。
  3. rwe并不是每个类都有,如需向类添加方法、分类、协议,以及设置版本时,才会对rwe进行初始化。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HelloCoder全栈小集 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引入问题
  • 分类的加载分析
  • 详解attachCategories方法
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档