Category的使用
Category是Objective-C语言中提供的一种灵活的类扩展机制。
使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 原封装 @interface Animal : NSObject { int _name; }
- (void)cry;
@end
@implementation Animal
- (void)cry { NSLog(@"Animal: cry"); }
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| // 原封装的Category @interface Animal (One) <NSCopying>
- (void)jump;
+ (void)name;
@property (nonatomic, assign) int age;
@end
@implementation Animal (One)
- (void)jump { NSLog(@"Animal: jump"); }
+ (void)name { NSLog(@"My name is Animal"); }
- (void)setAge:(int)age { }
- (int)age { return 10; }
@end
|
根据消息机制我们知道,实例的isa指针指向类对象,类对象的isa指针指向元类对象,当向实例发送cry消息时,通过实例的isa指针找到类对象,然后在类对象的methodLists中查找方法,如果没有找到,再通过类对象的super_class指针找到父类对象,接着去寻找cry方法,以此类推,直到找到根类对象为止。
那么当调用分类的方法时,步骤是否和原类一致呢?
分类中的对象方法依然是存储在类对象中的,同原类对象方法在同一个地方,调用步骤也同原类一样。如果是类方法的话,也同样是存储在元类对象中的。
那么分类方法是如何存储在原类对象中的,我们通过源码看一下分类的底层结构。
Category的底层结构
分类的方法不是在编译阶段合并至原来的类,而是在运行时合并的。
我们将OC的文件生成底层的C++实现文件
1
| xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Animal+One.m
|
Category结构体
在分类转化为C++文件中可以找到_category_t
结构体中,存放着类名、实例方法列表、类方法列表、协议列表以及属性列表。
1 2 3 4 5 6 7 8
| 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的实例方法列表结构体
存放实例方法列表结构体_method_list_t
,如下所示
1 2 3 4 5 6 7 8 9 10 11
| static struct /*_method_list_t*/ { unsigned int entsize; // 方法占用的内存 unsigned int method_count; // 方法数量 struct _objc_method method_list[3]; // 方法列表 } _OBJC_$_CATEGORY_INSTANCE_METHODS_Animal_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 3, {{(struct objc_selector *)"jump", "v16@0:8", (void *)_I_Animal_One_jump}, {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Animal_One_setAge_}, {(struct objc_selector *)"age", "i16@0:8", (void *)_I_Animal_One_age}} };
|
上面我们发现这个结构体 _OBJC_$_CATEGORY_INSTANCE_METHODS_Animal_$_One
从名称可以看出是INSTANCE_METHODS实例方法,并且一一对应为上面结构体赋值。我们可以看到结构体中存储了方法占用的内存,方法数量以及方法列表。并且可以找到分类中我们添加的jump、setAge、age三个实例方法。
Category的类方法列表结构体
存放类方法列表结构体_method_list_t
,如下所示
1 2 3 4 5 6 7 8 9
| 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_Animal_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 1, {{(struct objc_selector *)"name", "v16@0:8", (void *)_C_Animal_One_name}} };
|
同上面实例方法列表一样,这个我们可以看出是类方法列表结构体 _OBJC_$_CATEGORY_CLASS_METHODS_Animal_$_One
,同实例方法结构体相同,同样可以看到我们实现的类方法name。
Category的协议方法列表结构体
存放协议列表结构体_protocol_list_t
,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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; // 协议数量 struct _protocol_t *super_protocols[1]; // 存储协议方法 } _OBJC_CATEGORY_PROTOCOLS_$_Animal_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = { 1, &_OBJC_PROTOCOL_NSCopying };
|
通过上述源码可以看到先将协议方法通过_method_list_t
结构体存储,之后通过_protocol_t
结构体存储在_OBJC_CATEGORY_PROTOCOLS_$_Animal_$_One
中,同_protocol_list_t
结构体一一对应,分别为protocol_count
协议数量以及存储了协议方法的_protocol_t
结构体。
Category的属性列表结构体
存放属性列表结构体_prop_list_t
,如下所示
1 2 3 4 5 6 7 8 9
| static struct /*_prop_list_t*/ { unsigned int entsize; // 占用空间 unsigned int count_of_properties; // 属性数量 struct _prop_t prop_list[1]; // 属性列表 } _OBJC_$_PROP_LIST_Animal_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_prop_t), 1, {{"age","Ti,N"}} };
|
属性列表结构体_OBJC_$_PROP_LIST_Animal_$_One
,存储属性的占用空间、属性数量以及属性列表,可以看到我们自己添加的age属性。
分类_category_t结构体总结
最后我们可以看到定义的_OBJC_$_CATEGORY_Animal_$_One
结构体,并且将我们上面着重分析的结构体一一赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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; // 属性列表 };
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Animal;
static struct _category_t _OBJC_$_CATEGORY_Animal_$_One __attribute__ ((used, section ("__DATA,__objc_const"))) = { "Animal", 0, // &OBJC_CLASS_$_Animal, (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Animal_$_One, (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Animal_$_One, (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Animal_$_One, (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Animal_$_One, };
static void OBJC_CATEGORY_SETUP_$_Animal_$_One(void ) { _OBJC_$_CATEGORY_Animal_$_One.cls = &OBJC_CLASS_$_Animal; }
|
并且我们看到定义原类_class_t
类型的OBJC_CLASS_$_Animal
结构体,最后将分类_OBJC_$_CATEGORY_Animal_$_One
的cls指针指向原类OBJC_CLASS_$_Animal
结构体地址。由此可以得出,cls指针指向的是原类的类对象的地址。
源码分析
…
总结
Category
的实现原理是将其中的方法、属性、协议数据放在category_t
结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
通过之前对象的分析我们知道成员变量是实例的组成部分,并且编译的那一刻就已经决定好了,不能更改。而分类是在运行时才去加载的,那么我们就无法通过分类去改变实例的结构,因此分类中不可以添加成员变量。
Extension
和Categroy
不同在于:Extension
的信息是在编译的时候合并到原类对象中,而Categroy
是在运行时合并至原类对象中的。