【Objective-C】探索Category底层的实质
无论一个类设计的多么完美,在未来的需求演进中,都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢?一般而言,继承和组合是不错的选择。但是在objective-c 2.0中,又提供了category这个语言特性,可以动态地为已有类添加新行为。如今category已经遍布于objective-c代码的各个角落,从apple官方的framework到各个开源框架,从功能繁复的大型app到简单的应用,catagory无处不在。本文对category做了比较全面的整理,希望对读者有所裨益。
objective-c中类别特性的作用如下:
(1)可以将类的实现分散到多个不同文件或多个不同框架中(补充新的方法)。
(2)可以创建私有方法的前向引用。
(3)可以向对象添加非正式协议。
objective-c中类别特性的局限性如下:
(1)类别只能想原类中添加新的方法,且只能添加而不能删除或修改原方法,不能向原类中添加新的属性。
(2)类别向原类中添加的方法是全局有效的而且优先级最高,如果和原类的方法重名,那么会无条件覆盖掉原来的方法。
一、category的底层实现
objective-c 通过 runtime 运行时来实现动态语言这个特性,所有的类和对象,在 runtime 中都是用结构体来表示的,category 在 runtime 中是用结构体 category_t 来表示的,下面是结构体 category_t 具体表示:
typedef struct category_t { const char *name;//类的名字 主类名字 classref_t cls;//类 struct method_list_t *instancemethods;//实例方法的列表 struct method_list_t *classmethods;//类方法的列表 struct protocol_list_t *protocols;//所有协议的列表 struct property_list_t *instanceproperties;//添加的所有属性 } category_t;
从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
我们将结合 runtime 的源码探究下 category 的实现原理。打开 runtime 源码工程,在文件 objc-runtime-new.mm
中找到以下函数:
void _read_images(header_info **hlist, uint32_t hcount) { ... _free_internal(resolvedfutureclasses); } // discover categories. for (each_header) { category_t **catlist = _getobjc2categorylist(hi, &count); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; class cls = remapclass(cat->cls); if (!cls) { // category's target class is missing (probably weak-linked). // disavow any knowledge of this category. catlist[i] = nil; if (printconnecting) { _objc_inform("class: ignoring category \?\?\?(%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // process this category. // first, register the category with its target class. // then, rebuild the class's method lists (etc) if // the class is realized. bool classexists = no; if (cat->instancemethods || cat->protocols || cat->instanceproperties) { addunattachedcategoryforclass(cat, cls, hi); if (cls->isrealized()) { remethodizeclass(cls); classexists = yes; } if (printconnecting) { _objc_inform("class: found category -%s(%s) %s", cls->nameforlogging(), cat->name, classexists ? "on existing class" : ""); } } if (cat->classmethods || cat->protocols /* || cat->classproperties */) { addunattachedcategoryforclass(cat, cls->isa(), hi); if (cls->isa()->isrealized()) { remethodizeclass(cls->isa()); } if (printconnecting) { _objc_inform("class: found category +%s(%s)", cls->nameforlogging(), cat->name); } } } } // category discovery must be last to avoid potential races // when other threads call the new category code before // this thread finishes its fixups. // +load handled by prepare_load_methods() ... }
我们可以知道在这个函数中对 category 做了如下处理:
(1)将 category 和它的主类(或元类)注册到哈希表中;
(2)如果主类(或元类)已实现,那么重建它的方法列表;
category的实现原理:
- 在编译时期,会将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t ,然后通过这些结构体生成一个结构体 category_t 。
- 然后将结构体 category_t 保存下来
- 在运行时期,runtime 会拿到编译时期我们保存下来的结构体 category_t
- 然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中
- 将结构体 category_t 中的类方法列表、协议列表添加到主类的 metaclass 中
二、为何category中的方法优先级高于原类中的方法?
// 这里大概就类似这样子插入 newproperties->next = cls->data()->properties; cls->data()->properties = newproperties;,
三、为何category中不能添加实例变量?
通过结构体 category_t ,我们就可以知道,在 category 中我们可以增加实例方法、类方法、协议、属性。这里没有 objc_ivar_list 结构体,代表我们不可以在分类中添加实例变量。上一篇: 聊聊业务系统中投递消息到mq的几种方式
下一篇: PHP钩子与简单分发方式实例分析