思考:
- Category 和 Class Extension 的区别是什么?
- Category 的实现原理
Category 的底层结构
- Category 在编译完成后就变成了一个 _category_t 结构体,里面存储这分类的所有信息。
- 在程序运行时通过 Runtime 加载所有 _category_t 的数据,把所有 _category_t 的数据(方法、属性、协议)合并到一个大数组中。
- 合并时先扩充内存,然后将类对象里面的原有数据向后移动,再将分类数据(方法、属性、协议)插入到前排。
- 靠后被编译到的 _category_t 数据(方法、属性、协议),因为在这个大数组的前排,所以会被优先调用到。
定义 Persion+Test
|
|
Persion+Test.cpp
将 OC 代码转换为 C\C++ 代码,并在生成的 C/C++ 代码中找到 Persion+Test 的实现:
Category 的加载处理过程
objc4 源码解读过程
objc-os.mm
_objc_init
map_images
map_images_nolockobjc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
打开 runtime 源码 objc4-781。找到运行时入口 objc-os.mm 文件,打开文件找到运行时的初始化方法 void _objc_init(void)
方法:
_objc_init
|
|
map_images
Jump To Definition -> map_images:
map_images 方法的主要作用是处理由dyld映射的镜像文件.
map_images_nolock
Jump To Definition -> map_images_nolock:
_read_images:images 是模块/镜像的意思。该方法是读取模块用的,比如读取类信息、分类信息等。
_read_images
Jump To Definition -> _read_images:
上面👆代码是 _read_images 处理 Category 的核心代码,首先通过 _getObjc2NonlazyClassList 方法获取 Category 数组,遍历 Category 数组通过 remapClass 获取分类对应的指针,作为参数调用 realizeClassWithoutSwift 方法。
realizeClassWithoutSwift
Jump To Definition -> realizeClassWithoutSwift:
realizeClassWithoutSwift 方法内部进过一顿骚操作,最后调用 methodizeClass 方法开始加载分类信息。
Attach categories:将分类信息附加到类对象里。
methodizeClass
Jump To Definition -> methodizeClass:
Install methods and properties that the class implements itself. 可以看出该方法是在加载类对象自己内部的信息。分别针对方法数组、属性数组和协议数组调用 attachLists 方法,将信息添加到类对象里。
attachToClass
Jump To Definition -> attachToClass:
attachCategories
Jump To Definition -> attachCategories:
分别针对方法数组、属性数组和协议数组调用 attachLists 方法,将分类信息添加到原类对象里。
attachLists
Jump To Definition -> attachLists:
realloc、memmove、memcpy
attachLists 方法内部
1.通过参数 addedCount 确定需要增加的内存空间 newCount,然后通过 realloc 方法重新分配空间。
2.空间增加后,通过 memmove 方法将类对象里的信息往后移动 addedCount 距离,把前排 addedCount 大小的空间空出来留给将要添加进来的分类信息。
3.类对象的前排空间空出来后,再通过 memcpy 方法将分类信息拷贝到该空间里。
Category 的编译顺序
原文件 Persion.m 最先编译,之后添加的分类按照添加顺序,优先编译后来添加的分类。添加顺序:
如图编译顺序按照 Compile Sources 里的顺序,为 Persion.m -> Persion+Test.m -> Persion+Demo.m。如果 Persion.m、Persion+Test.m、Persion+Demo.m 中有相同的方法,后编译的类中的方法会优先于先编译的类中的方法(不是覆盖,只是优先被查询到)。
调用日志:
可以看到 Persion.m 被优先编译,其次是后来添加的 Persion+Test.m,第三个编译的是 最后添加的 Persion+Demo.m。添加顺序决定编译顺序只对分类有效,Persion.m 一定是最先编译的,这一点通过 methodizeClass 方法也能看出来,先处理类对象,再处理分类。
Class Extension 的实现原理
定义 Persion()
|
|
将 OC 代码转换为 C\C++ 代码,并在生成的 C/C++ 代码中找到 Persion 的实现:
上面👆这块代码可以看到,在 Persion() 中定义的 categoryTest2 已经包含在类 Persion 类对象的结构体里面了。可以证明 Class Extension 在编译的时候,它的数据就已经包含在类信息中了。从实现上来看,Class Extension 不应该叫做匿名分类,叫类扩展更适合。
小结
- 运行时入口 objc-os.mm,初始化方法 void _objc_init(void) 方法。
- objc4 的代码虽然改了,但是实现原理还是没变。
总结
- Category 的实现原理
Category 编译之后的底层结构是 struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息,在程序运行的时候,runtime 会将 Category 的数据,合并到类信息中(类对象、元类对象中)。 - Category 和 Class Extension 的区别是什么?
Class Extension 在编译的时候,它的数据就已经包含在类信息中。Category 是在运行时,才会将数据合并到类信息中。