先来看一下分类的本质是什么, 还是老方法, 将分类编译成C++
文件.
首先, 新建一个Person
的分类:
具体实现如下:
Person.h
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@protocol DrinkProtocol <NSObject>
- (void)drink;
@end
@interface Person (Eat)
@property (nonatomic, strong) NSString *name;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person+Eat.h"
@implementation Person (Eat)
- (void)eat {
NSLog(@"eat");
}
+ (void)run {
NSLog(@"run");
}
@end
我在分类里面, 有协议, 有属性, 有方法, 那么我们将它编译成C++
文件, 看看是什么样的.
在C++
文件里, 搜索_category_t
, 发现:
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat,
};
这是结构体的赋值情况
那这个结构体的定义是什么呢?
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;
};
上下对比就可以发现
赋值情况是: 将Person
赋值给name
, cls
是0
, 然后实例方法着重说一下, 它是将_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat
这样一个结构体的地址赋值给instance_methods
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat
的定义和赋值是怎样的呢?
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_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_Eat_eat}}
};
这个结构体的定义是
struct _method_list_t {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
}
第一个成员变量是entsize
, 是struct _objc_method
这个结构体的大小.
struct _objc_method {
struct objc_selector * _cmd; // SEL (一般是方法名(struct objc_selector *)"eat", 将"eat"的`char*`强转成`struct objc_selector *`)
const char *method_type; // 方法类型
void *_imp; // 实现的指针
};
第二个成员变量是method_count
, 方法数量, 在这个例子里, 方法数量是1
;
第三个成员变量是struct _objc_method
类型的结构体数组. 这个结构体数组里面存的是每一个方法结构体, 方法结构体存的是方法名, 方法类型, 和方法实现(指针)
然后类方法是将_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat
这样一个结构体地址赋值给class_methods
, 这个和instance_methods
是差不多的.
protocols
是0
,
properties
是_OBJC_$_PROP_LIST_Person_$_Eat
这样一个结构体的地址, 我们来看看这个结构体
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_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"name","T@\"NSString\",&,N"}}
};
这个和方法结构体很像:
struct _prop_list_t {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
}
struct _prop_t {
const char *name; // 属性名称
const char *attributes; // 属性的特征(类型等)
};
{
"name",
"T@\"NSString\",&,N"
}
从上面可以看出, 分类的本质就是一个个的struct _category_t
结构体.创建一个分类, 就会在内存中生成一个struct _category_t
结构体. 在编译阶段就是如此
那么在运行时阶段, 分类又会怎么样呢?
- 通过
runtime
加载某个类的所有category
数据 - 把所有
category
的方法, 属性, 协议数据, 合并到一个大数组中, 后面参与 编译的category
数据, 会在数据前面 - 将合并后的分类数据(方法, 属性, 协议), 插入到类原来数据的前面