分类在我们的项目中经常被使用到,它是给现有的类添加方法,也可用于根据功能划分模块,今天我们就来研究一下分类实现的原理.
❓比如说有如下3个类,思考一下如下图所示的实例方法存放在哪里?
我们之前研究OC对象的本质时已经知道了,实例方法存放在类对象中,类方法存放在元类对象中,也就是说
- thought:
方法存放在HHPerson
类对象中.那么分类中的方法存放在哪里呢?莫非-eat:
方法存放在HHPerson+eat
类对象中,-run:
方法存放在HHPerson+run
类对象中?事实上,一个类在内存中只存在一个类对象,
-thought:
,-eat:
,-run:
这三个实例方法在编译期都存放在struct _category_t
这个结构体中,等到运行期利用runtime
机制动态合并到HHPerson
这个类对象中.
下面我们就来验证一下上面的结论.
-
一:窥探Category 底层数据结构
我们写一个分类HHPerson+eat
,然后转换成.cpp
文件,发现Category
底层被转换为struct _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;//属性列表
};
我们的分类HHPerson+eat
被转换为类型为static struct _category_t
,变量名为:_OBJC_$_CATEGORY_HHPerson_$_eat
:
static struct _category_t _OBJC_$_CATEGORY_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"HHPerson",
0, // &OBJC_CLASS_$_HHPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HHPerson_$_eat,
0,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_HHPerson_$_eat,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HHPerson_$_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_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_HHPerson_eat_eat}}
};
协议列表: (HHPerson+eat实现了NSCopying,NSCoding
协议)
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying,
&_OBJC_PROTOCOL_NSCoding
};
属性列表:
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_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"age","Ti,N"}}
};
以上就是分类的底层结构,可以看到,分类的信息在编译期间都被分离出来了,下面我们从runtime
源码研究一下分类.
-
二:从Runtime源码查看Category
打开runtime
源码,按照以下步骤查看:
- 搜索并打开
objc-os.mm
源文件,找到void _objc_init(void)
方法.此方法是Runtime初始化入口. - 点击进入
map_images
:
- 点击进入
map_images_nolock
:
- 点击进入
_read_images
(现在开始已经进入加载模块):
- 在
_read_images
中找到// Discover categories.
(搜索分类),我们重点研究这里:
检索分类
- category_t **catlist 是一个二位数组,里面存放的使我们给一个类创建的所有分类,比如上面我们给
HHPerson
类添加了三个分类HHPerson +eat
,HHPerson +run
,HHPerson +thought
就存放在这个二维数组中:[[category_t_eat],[category_t_run],[category_t_thought]]
. -
remethodizeClass()
;重新组织方法,传入类对象就是重新组织实例方法,传入cls->ISA()
就是重新组织类方法.
- 点击进入
remethodizeClass()
,找到attachCategories(cls, cats, true /*flush caches*/);
附加分类 - 点击进入
attachCategories(cls, cats, true /*flush caches*/);
:
attachCategories方法内部逻辑
我们梳理一下attachCategories (cls,cats,true)
方法.attach
一词是附加
的意思,从名字上我们可以看出这个方法大概意思是:附加分类
.事实上它的确如此,下面我开始研究:
- 参数分析:
-
cls
, 分类的本类,就是我们现在HHPerson
-
cats
, 分类列表,[category_t (HHPerson+run),category_t (HHPerson+eat), category_t (HHPerson+thought)]
.
-
首先分配内存,创建三个二维数组
mlists
,proplists
,protolists
分别用来存放方法列表
,属性列表
,协议列表
-
通过while循环遍历分类列表
cats
,取出某一个分类. -
未完待续...
网友评论