分类的实现原理,编译时的表现,运行时的加载原理
参考文档: objc4-723
category的主要作用是为已经存在的类添加方法。可以把类的实现分开在几个不同的文件里面。
Category 结构体
Category用Category_t结构体定义:
1 | typedef struct category_t { |
从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
Category 与 Extension
extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
但是category则完全不一样,它是在运行期决议的。
就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
Category编译时
写个简单的Category:
1 | //.h |
然后使用Clang命令编译下,命令如下:
1 | clang -rewrite-objc OneClass.m |
然后得到一个3m多的OneClass.app文件……
在OneClass.app中找到相关的代码片段:
生成了实例方法列表:
1 | static struct /*_method_list_t*/ { |
生成了属性列表:
1 | static struct /*_prop_list_t*/ { |
生成了category本身 ,并用前面的实例方法列表和属性方法列表初始化
1 | static struct _category_t _OBJC_$_CATEGORY_OneClass_$_OneCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = |
最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$(当然,如果有多个category,会生成对应长度的数组),用于运行期category的加载。
1 | static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { |
Category运行期加载
objc_init 和 map_images
在OC运行时入口方法_objc_init
中((在objc-os.mm文件中)):
1 | void _objc_init(void) |
具体点大概是这么个流程:
1 | _objc_init -> map_images -> map_images_nolock -> _read_images |
read_images
_objc_init
里面的调用的map_images最终会调用objc-runtime-new.mm里面的_read_images
方法,而在_read_images
方法的结尾,有以下的代码片段:
1 | // Discover categories. |
从代码中可以看到remethodizeClass处理添加事宜方法
remethodizeClass && attachCategories
既然remethodizeClass才是真正去处理添加事宜方法那么现在看一下remethodizeClass:
1 | static void remethodizeClass(Class cls) |
emmm 好像remethodizeClass也没干什么事,都交给了attachCategories
方法了嘛,接下来看下attachCategories:
1 | static void |
这个函数的主要作用是将 Category 中的方法、属性和协议整合到类(主类或元类)中,更新类的数据字段 data() 中 method_lists(或 method_list)、properties 和 protocols 的值。
- category的方法没有替换掉原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
- category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休。
- 怎么调用到原来类中被category覆盖掉的方法?对于这个问题,我们已经知道category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法