013:分类的加载:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->load_categories_nolock->attachCategories, loadImage的加载

问题

懒加载分类会在编译器把数据加载

只要有非懒加载分类会走attachCategories来加载分类数据

1:methodizeClass内部有对分类的处理

2:attachToClass

3:attachCategories函数:绑定分类。

4:extAllocIfNeeded:-> extAllocIfNeeded

5:  extAlloc-->>初始化rwe

6:prepareMethodLists函数排序

7:fixupMethodList:  修复函数

8:attachLists:完成数据的绑定

分类的加载,总得来说有2个大的调用路径

1:map_images-> map_images_nolock-> _read_images有2个可能路径:

  路径一: 第8步 分类的处理-> load_categories_nolock-> attachCategories

  路径二: 第9步 实现非懒加载类-> realizeClassWithoutSwift-> methodizeClass-> attachToClass-> attachCategories

2:load_images-> loadAllCategories-> load_categories_nolock-> attachCategories,  非懒加载类和非懒加载分类

 

目录

 

预备

 

正文

1:分类的本质

1:通过 objc 源码搜索 category_t

 

 2:分类中定义的方法:methodlist

其中有一个方法,格式为:sel+签名+函数地址。这个就是我们前面分析过的 method_t的结构,我们在 objc 源码里面搜索 method_t

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

  name 对应 sel

  type 对应 方法签名

  imp 对应 函数地址 

 3:分类中定义的属性

 同时,我们也注意到一个问题,在分类中,我们定义了两个属性,但是编译成c++之后,并没有看到相关属性,这是因为 分类中定义的属性没有相应的set和get方法的实现,只有方法的声明。

4:总结

分类的本质就是一个 category_t的结构体类型

2、分类的加载

我们先给 LGPerson 创建两个分类 LGA 和 LGB

1:realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock ->extAllocIfNeeded->attachCategories->attachLists(将方法加载到class())中提及了rwe的加载,

2:其中分析了分类的data数据 时如何 加载到中的,且分类的加载顺序是:LGA -> LGB的顺序加载到类中,即越晚加进来,越在前面

其中查看methodizeClass的源码实现,可以发现类的数据和 分类的数据是分开处理的,主要是因为在编译阶段,就已经确定好了方法的归属位置(即实例方法存储在中,类方法存储在元类中),而分类是后面才加进来的

其中分类需要通过 attachLists方法添加到类之后,外界才能进行调用,在此过程,我们已经知道了分类加载三个步骤中的后面两个步骤

  • 分类数据 加载时机: 根据 类和分类是否实现 load 方法来区分不同的时机。
  • 在 attachCategories方法中准备分类数据
  • 在 attachLists中将分类数据添加到主类 

分类的数据加载时机的反推路径为:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images

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

 

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

4:extAllocIfNeeded初始化rwe。1.分类 2.addMethod 3.class_addProtocol 4._class_addProperty

3、类和分类的搭配使用

5.1 非懒加载类 + 非懒加载分类

  • 类的数据加载是通过 _getObjc2NonlazyClassList加载,即对 rorw的操作和对 rwe赋值初始化,在extAlloc方法中
  • 分类的数据加载是通过 load_images加载到类中的

调用路径为

  • map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass,此时的 mlists是一维数组,然后走到 load_images部分。
  • load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists,此时的 mlists是二维数组。

总结

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

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

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

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

 

4:attachClass

memcpy(开始位置,放置内容,占用大小) : 内存拷贝

memmove(开始位置,移动内容,占用大小): 内存平移

LRU算法:

  • Least Recently Used的缩写,最近最少使用算法越容易被调用(访问)的放前面

  • 回想一下,不管我们是动态插入函数,还是添加分类,一定是有需求时才这么操作。而新加入的数据,明显访问频率高于默认模板内容。所以我们addedLists使用LRU算法,将旧数据放在最后面新数据永远插入最前面。 这样可以提高查询效率减少运行时资源的占用

0->1: 首次加入,直接将addedLists[0]赋值给list,是一维数组
(首次加载是本类数据在extAllocIfNeeded时,从macho读取ro中的对应数据加入
 


- 1->多: 此时扩容为二维数组旧数据插入后面新数据插入前面:
将数组扩容newCount大小
-> array()count记录个数
-> 如果有旧数据插入lists容器尾部
-> 调用memcpy内存拷贝,从array()首地址开始,将addedLists插入,占用addedCount个元素大小。
 
- 多 -> 更多: 类似于1->多的操作,也是旧数据移到后面新数据插入前面
将数组扩容newCount大小
-> array()count记录个数
-> 调用memmove内存平移,从array()首地址偏移addedCount个元素位置开始,移动array()旧数据,占用oldCount个元素大小
-> 调用memcpy内存拷贝,从array()首地址开始,将新数据addedLists插入,占用addedCount个元素大小。
所以这里rwe函数、属性、协议都是attachLists进行处理后完成的赋值。

5:load_images原理分析

load_images方法的主要作用是加载镜像文件,其中最重要的有两个方法:prepare_load_methods(加载) 和 call_load_methods(调用)

1:load_images源码实现

2: 进入prepare_load_methods源码

3:add_class_to_loadable_list,主要是将load方法和cls类名一起加到loadable_classes表中 

 

 4:进入getLoadMethod,主要是获取方法的sel为load的方法

 

5:_getObjc2NonlazyCategoryList -> realizeClassWithoutSwift -> add_category_to_loadable_list,主要是将非懒加载分类的load方法加入表中

6:进入add_category_to_loadable_list实现,获取所有的非懒加载分类中的load方法,将分类名+load加入表loadable_categories

进入call_load_methods源码,主要有3部分操作
  • 反复调用类的+load,直到不再有

  • 调用一次分类的+load

  • 如果有类或更多未尝试的分类,则运行更多的+load、

2:进入call_class_loads,主要是加载类的load方法
 
3:call_category_loads,主要是加载一次分类的load方法
 
 
综上所述,load_images方法整体调用过程原理图示如下

主要分为两步

  • 从所有的非懒加载类和分类中的+load分别添加到表
  • 调用类和分类的+load方法
6:rwe的加载,是执行了extAlloc方法,所以我们反向搜索,查看谁调用extAlloc方法:

只有extAllocIfNeededdeepCopy调用了。

  • deepCopy深拷贝: 搜索deepCopy(,发现只被objc_duplicateClass调用,而是objc_duplicateClass开放使用的API接口,并没自动调用的地方。 所以此处不做考虑。

  • extAllocIfNeeded: 搜索extAllocIfNeeded(,发现有以下7处调用了它:

 
注意

1.1 本类+load,分类无

提取信息如下:

  1. 路径: 是map_images调用的
  2. ro函数列表:此时ro读取的是macho中的,ro中已包含本类和所有函数信息(14个)。
  3. 函数排序: 分类的函数不会覆盖本类的同名函数,而是后加载的分类函数排序在先加载的分类和本类前面

放开断点,继续运行,发现没有进入attachCategories内部。

结论:【本类+load,分类无】的情况:数据在编译层已经加入data中。

1.2. 本类+load,分类+load

提取信息如下:

  1. 路径: 是map_images调用的
  2. ro函数列表:此时ro读取的是macho中的,ro中仅有HTPerosn本类函数信息(8个)。

attachCategories->attachLists

1.3: 本类无,分类无

继续运行代码,没有进入attachCategories中。

1.4. 本类无,分类+load

attachCategories->attachLists,提前加载数据

1.5:本类,分类A ,分类B+load

发现ro加载好本类和2个分类所有数据(14个函数),没有再进入attachCategories了。

引用 

posted on 2020-12-02 17:10  风zk  阅读(214)  评论(0编辑  收藏  举报

导航