category的实现原理
在上一篇文章iOS runtime中提到了class_rw_t这个结构,在category中的写的方法,协议,属性等会在程序运行经由runtime写入类的class_rw_t中。这是怎么处理的呢?
首先得看下category编译后的结构。新建一个NSObject的category printCategory.使用clang命令行编译出category的c++实现。
NSObject+printCategory.m文件如下
#import "NSObject+printCategory.h"
@implementation NSObject (printCategory)
+(void)printCategory{
NSLog(@"category测试");
}
@end
clang命令行
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 分类的路径
得出NSObject+printCategory.cpp文件,cpp文件中代码较多,抽取其中的关键代码,我们编写的category代码最终帮我们生成了 struct _category_t类型的 _OBJC__CATEGORY_NSObject__printCategory的值
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_printCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printCategory", "v16@0:8", (void *)_C_NSObject_printCategory_printCategory}}
};
static struct _category_t _OBJC_$_CATEGORY_NSObject_$_printCategory __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS_$_NSObject,
0,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_printCategory,
0,
0,
};
其中结构体_category_t的定义如下
struct _category_t {
const char *name;//被添加分类的类名称
struct _class_t *cls;
const struct _method_list_t *instance_methods;//实例方法列表
const struct _method_list_t *class_methods;//类方法列表
const struct _protocol_list_t *protocols;//协议列表
const struct _prop_list_t *properties;//属性列表
};
在编译过程中,编译器把category编译成上面的内容,然后运行时候经由runtime把_OBJC__CATEGORY_NSObject__printCategory里面的内容合并到类信息class_rw_t这个结构体中。
对于我们自定义的_OBJC__CATEGORY_NSObject__printCategory这个category,会把class_methods里面的值,也就是OBJC_printCategory结构题里面的方法列表,合并到类信息class_rw_t中,大概的流程图如下
![](https://img.haomeiwen.com/i2264391/487f005de342e852.png)
class_rw_t 中的method_lists是一个二维数组,对于数组的每一行,存储一个文件里面的方法列表。如类的原有方法列表会存储到一个一维数组里面,printCategory这个category里面的方法会存储到另外一个一维数组里面。
当把分类的方法合并到method_lists里面时候,往method_lists里面插入一个分类的方法的数组。
runtime在合并category的方法列表的时候,会把类所有的category编译出来的方法数组加载出来,统计一共有多少个一维数组,假设一维数组的数量为n,然后把类原有的方法数组往后挪动n位。再把category里面的方法数组拷贝到method_lists数组里面。对于拷贝的顺序,越往后编译的category方法数组列表越靠前。
如下图中NSObject有两个category, printCategory 和 printCategory2。
![](https://img.haomeiwen.com/i2264391/c7750d8d8d0819bc.png)
printCategory排在printCategory2前面,在method_lists列表中数组的顺序printCategory的方法数组列表比printCategory2的靠前。
![](https://img.haomeiwen.com/i2264391/fc131b7758482e54.png)
当我们调用NSObject 的printCategory方法的时候,会去method_lists从前往后遍历查找方法。当在printCategory的分类方法数组中找到printCategory方法时,会停止查找方法,并把刚才找到的方法放到类的方法缓存中,并执行方法。所以即便printCategory2分类中也存在printCategory方法,也不会执行到。
如果分类中存在与类原有的相同的方法,分类中的方法会“覆盖”掉类原有的方法。如果有多个分类中存在同一个方法,调用时执行最后编译的分类方法。
网友评论