Category的实现原理
Category中对象方法,在程序运行过程中,都会进入类的对象方法列表中。实例变量都是在自己的类对象的方法列表中查找方法。
Category中类方法,在程序运行过程中,都会进入元类的对象方法列表中。实例变量都是在自己的元类对象的方法列表中查找方法。
Category 源码
将Objective-C编译成C++代码的命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Test.m -o Person+Test.cpp
编译前:
@interface Person (Test)<NSCopying>
@property (nonatomic, strong) NSString *xsysString;
- (void)test;
@end
@implementation Person (Test)
- (void)run {
NSLog(@"test");
}
+ (void)test {
}
@end
编译后:
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; // 属性
};
// 实例方法结构体
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Person_Test_run}}
};
// 类方法结构体
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_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_C_Person_Test_test}}
};
// 协议
static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"@24@0:8^{_NSZone=}16"
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};
struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0,
"NSCopying",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
// 属性
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"xsysString","T@\"NSString\",&,N"}}
};
// 分类结构体赋值
static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person", // 变量名赋值
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test, // 实例方法结构体
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test, // 类方法结构体
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test, // 协议
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test, // 属性
};
当程序一编译的时候,cagtegory中的数据会编译成_category_t结构体,数据存储在当前结构体内。
runtime中Category源码解读
过程分析:
控制编译顺序:compile source中的文件顺序就是编译顺序
最后面参与编译的分类会放在前面,同样的方法会优先调用分类的内的方法
Category加载过程
我试图追中源码,但是根据下面的过程,没有全部追踪到。可能是版本的问题。不过思路都是一样的,仅供参考

Category合并到类中的过程:
运行时会通过remethodizeClass(class *cls)和 remethodizeClass(cls->ISA)方法将分类的信息分别合并到class对象和meta-class对象中。
合并的步骤:
1、类的分类按照编译顺序会存储在一个数组中category_list [_category_t,_category_t]
2、倒序遍历分类的数组(这也是最后编译的分类生效的原因)。并将分类的方法列表,属性列表,协议列表存储在对应的二维数组中。
// 二维数组
method_list_t **mlists = ; // 分类中的方法列表
property_list_t **proplists = ; // 分类中的属性列表
protocal_list_t **protlists = ; // 分类中的协议列表
i= category_list->count
mcount = 0
propcount = 0
protcount = 0
while(i--) { // 倒叙遍历分类
category = category_list->list[i]
/*
[
[method_t,method_t],
[method_t,method_t]
]
*/
method_list_t *mlist = category->methods
if(mlist) {
mlists[mcount ++] = mlist
}
/*
[
[property_t,property_t],
[property_t,property_t]
]
*/
property_list_t *proplist = category->porperities
if(proplist) {
proplists[propcount ++] = mlist
}
/*
[
[protocol_t,protocol_t],
[protocol_t,protocol_t]
]
*/
protocol_list_t *protlist = category->protocls
if(protlist) {
protlists[protcount ++] = mlist
}
}
3、将方法列表协议列表属性列表合并到class对象或meta-class对象中
rw->methods.attchLists(mlist,mcount);
rw->properties.attchLists(proplist,propcount);
rw->protocol.attchLists(protlist,protcount);
4、attchLists过程:
将原来的对象方法指针向后移动分类的个数的位置,在把空出的位置指向新的分类的方法列表。因为分类是倒序遍历,并将方法取出来存储在二维数组中的,所以最后编译的分裂的方法会生效。

所以同样的方法会优先调用分类的内的方法
分类方法为什么会覆盖原来的方法?分类方法是真的覆盖原来的方法吗?
会是原来的方法不生效。不是真正的覆盖,因为先找到了分类中的方法,不会再往后找而已。
Category对象方法合并到Class对象方法列表中,Category的类方法合并到meta-class的类方法列表中
Category与Extension的区别是什么
分类实现原理:
底层结构是 _category_t结构体,里面存储了分类的对象方法,类方法,属性信息,协议信息,在程序运行的时候,runtime会将Category里的数据合并到类信息中(类对象、元类对象中)
Extension实现原理
扩展在编译器的时候就已经合并到类里了
区别:
扩展在编译的时候数据就已经包含在类信息中,category在运行期才会将数据合并到类信息中。
网友评论