1、Category简介
一个类有不同的功能,可以创建不同的分类进行区分。
比如有一个Person类,自己有一个run方法:
@interface Person : NSObject
- (void)run;
@end
给Person类新建两个分类:Person+Test.h和Person+Eat.h,并分别添加tes和eat方法方法:
@interface Person (Test)
- (void)test;
@end
@interface Person (Eat)
- (void)eat;
@end
导入Person.h和Person+Test.h头文件后,即可调用run和test方法
Person *person = [[Person alloc] init];
[person run];
[person test];
[person eat];
思考:如果person对象想调用test方法,需要通过isa指针找到类对象,在类对象中的方法列表中找到test方法进行调用,那么test方法是如何从分类中合并到主类中的?
2、Category的底层结构
将Person+Test.m转成.cpp文件(参考:OC代码转换),可以看到分类的结构体类型_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_t创建的Test分类
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,
0,
0,
0,
};
总结:每个分类都会在编译时生成一个_category_t类型的结构体,用来保存分类里的信息。
3、Cagegory的加载过程
通过查看 objc源码 ,在objc-runtime-new.mm中的attachCategories方法
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags)
传入类对象和分类列表(数据结构可能是:cls -> [Person class] 和 cats_list -> [category_t、category_t、…]),方法内部定义了三个数组,用于保存主类所有分类的方法、属性和协议信息。
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
再遍历所有分类结构体,将分类里的信息保存到三个数组中,最后再将数组中的数据,附加到主类的数据中
// 获取主类对象的数据rwe
auto rwe = cls->data()->extAllocIfNeeded();
// 将所有分类的对象方法附加到主类对象的对象方法列表中
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
// 将所有分类的属性附加到主类对象的属性列表中
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
// 将所有分类的协议附件到主类对象的协议列表中
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
attachLists方法的工作原理类似于动态数组的扩容过程,将插入的数据到原列表的前面。根据attachCategories分类遍历顺序,后编译的分类会放在数组(方法、属性、协议)的前面位置,以Person的方法列表为例,合并过程如下:
*实际上合并后的方法列表是个二维数组,这里为了方便观察顺序,图中没有体现。
Test分类因为是最后编译,所以test方法会放在合并后的方法列表里的最前面,且分类方法都会在主类方法前。
(分类的编译顺序由Xcode-Target-Build Phases-Complie Sources中文件排列顺序决定,排在上面的文件优先编译)
总结:
1、通过Runtime加载某个类的所有Category数据
2、把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面
3、将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
4、如果分类中实现了主类方法,分类中的方法会被优先调用,如果多个分类都实现了,则后编译的分类优先被调用
4、Class Extension
Class Extension(类扩展)主要用于声明一些私有方法和属性
@interface Person ()
@property (nonatomic, assign) int age;
- (void)jump;
@end
Class Extension在编译的时候,它的数据已经包含在类信息中。
Category是在运行时,才会将数据信息合并到类信息中。