iOS-底层原理 17:应用程序启动加载之分类加载

本篇主要研究分类是如何加载中的,以及分类和类搭配使用的情况

分类的本质

前提:在main中定义LGperson的分类LG

我们通过clang探索分类的本质

  • clang -rewrite-objc main.m -o main.cpp 查看底层编译,即 main.cpp,

    • 其中分类的 类型是_category_t
    • 分类的倒数第二个0,表示的是没有协议,所以赋值为0
  • 搜索struct _category_t,如下所示

    • 其中有两个method_list_t,分别表示实例方法类方法
  • 搜索_CATEGORY_INSTANCE_METHODS_LGPerson_,找到其底层实现

  • 其中有3个方法,格式为:sel+签名+地址,是method_t结构体的属性即key

  • 搜索method_t,其中对应关系如下

    • name 对应 sel
    • type 对应 方法签名
    • imp 对应 函数地址

同时,我们发现了一个问题:查看看_prop_list_t,明明分类中定义了属性,但是在底层编译中并没有看到属性,如下图所示,这是因为分类中定义的属性没有相应的set、get方法,我们可以通过关联对象来设置(关于如何设置关联对象,我们将在后续的扩展中进行说明)

总结

综上所述,分类的本质 是一个_category_t类型

  • 有两个属性:name(类的名称)cls(类对象)

  • 有两个 method_list_t类型的方法列表,表示分类中实现的实例方法+类方法

  • 一个protocol_list_t类型的协议列表,表示分类中实现的协议

  • 一个prop_list_t类型的属性列表,表示分类中定义的属性,一般在分类中添加的属性都是通过关联对象来实现

  • 需要注意的是,分类中的属性是没有set、get方法

分类的加载

前提:创建LGPerson的两个分类:LGA、LGB

在上一篇文章中的realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories中提及了rwe的加载,其中分析了分类的data数据 时如何 加载到中的,且分类的加载顺序是:LGA -> LGB的顺序加载到类中,即越晚加进来,越在前面

其中查看methodizeClass的源码实现,可以发现类的数据分类的数据是分开处理的,主要是因为在编译阶段,就已经确定好了方法的归属位置(即实例方法存储在中,类方法存储在元类中),而分类是后面才加进来的其中分类需要通过attatchToClass添加到类,然后才能在外界进行使用,在此过程,我们已经知道了分类加载三步骤的后面两个步骤,分类的加载主要分为3步: 

  • 分类数据加载时机:根据类和分类是否实现load方法来区分不同的时机

  • attachCategories准备分类数据

  • attachLists分类数据添加到主类

分类的加载时机

下面我们来探索分类数据的加载时机,以主类LGPerson + 分类LGA、LGB 均实现+load方法为例

通过第二步数据准备反推第一步的加载时机

  • 通过上一篇文章我们了解到,在走到attachCategories方法时,必然会有分类数据的加载,可以通过反推法查看 在什么时候调用attachCategories的,通过查找,有两个方法中调用

    • load_categories_nolock方法中
    • addToClass方法中,这里经过调试发现,从来不会进到if流程中,除非加载两次,一般的类一般只会加载一次
  • 不加任何断点,运行objc代码,可以得出以下打印日志,通过日志可以发现addToClass方法的下一步就是load_categories_nolock方法就是加载分类数据

  • 全局搜索load_categories_nolock的调用,有两次调用

    • 一次在loadAllCategories方法中
    • 一次在_read_images方法中 
  • 但是经过调试发现,是不会走_read_images方法中的if流程的,而是走的loadAllCategories方法中的

  • 全局搜索查看loadAllCategories的调用,发现是在load_images时调用的

通过堆栈信息分析

  • attachCategories中加自定义逻辑的断点,bt查看堆栈信息

 所以综上所述,该情况下的分类的数据加载时机的反推路径为:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images

而我们的分类加载正常的流程的路径为:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories

我们再来看一种情况:主类+分类LGA实现+load,分类LGB不实现+load方法

  • 断点定在attachCategories中加自定义逻辑部分,一步步往下执行

    • p entry.cat
    • p *$0
  • 继续往下执行,会再次来到 attachCategories方法中断住

    • p entry.cat
    • p *$0

     总结只要有一个分类是非懒加载分类,那么所有的分类都会被标记位非懒加载分类,意思就是加载一次 已经开辟了rwe,就不会再次懒加载,重新去处理 LGPerson

分类和类的搭配使用

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

类+分类
分类实现+load分类未实现+load
类实现+load非懒加载类+非懒加载分类非懒加载类+懒加载分类
类未实现+load懒加载类+非懒加载分类懒加载类+懒加载分类
  • 【情况1】非懒加载类 + 非懒加载分类

  • 【情况2】非懒加载类 + 懒加载分类

  • 【情况3】懒加载类 + 懒加载分类

  • 【情况4】懒加载类 + 非懒加载分类

总结懒加载类 + 非懒加载分类的数据加载,只要分类实现了load,会迫使主类提前加载,即 主类 强行转换为 非懒加载类样式

总结

类和分类搭配使用,其数据的加载时机总结如下:

  • 【情况1】非懒加载类 + 非懒加载分类,其数据的加载在load_images方法中,首先对类进行加载,然后把分类的信息贴到类中

  • 【情况2】非懒加载类 + 懒加载分类,其数据加载在read_image就加载数据,数据来自data,data在编译时期就已经完成,即data中除了类的数据,还有分类的数据,与类绑定在一起

  • 【情况3】懒加载类 + 懒加载分类 ,其数据加载推迟到 第一次消息时,数据同样来自data,data在编译时期就已经完成

  • 【情况4】懒加载类 + 非懒加载分类 ,只要分类实现了load,会迫使主类提前加载,即在_read_images中不会对类做实现操作,需要在load_images方法中触发类的数据加载,即rwe初始化,同时加载分类数据

如下图所示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值