Hello, World!

Category

字数统计: 1.6k阅读时长: 7 min
2018/06/23 Share

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结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。

通过之前对象的分析我们知道成员变量是实例的组成部分,并且编译的那一刻就已经决定好了,不能更改。而分类是在运行时才去加载的,那么我们就无法通过分类去改变实例的结构,因此分类中不可以添加成员变量。

ExtensionCategroy不同在于:Extension的信息是在编译的时候合并到原类对象中,而Categroy是在运行时合并至原类对象中的。

CATALOG
  1. 1. Category的使用
  2. 2. Category的底层结构
    1. 2.1. Category结构体
    2. 2.2. Category的实例方法列表结构体
    3. 2.3. Category的类方法列表结构体
    4. 2.4. Category的协议方法列表结构体
    5. 2.5. Category的属性列表结构体
    6. 2.6. 分类_category_t结构体总结
  3. 3. 源码分析
    1. 3.1. 总结