2019-10-15
本文介绍分类(Category)的实现以及分类的加载过程。分类是对 Objective-C 类的一种扩展方式。说到分类不可不提扩展(Extension)。扩展通常被视为匿名的分类,但是两者实现的区别还是很大的:
- 扩展只是对接口的扩展,所有实现还是在类的
@implementation
块中,分类是对接口以及实现的扩展,分类的实现在@implementation(CategoryName)
块中; - 分类在 Runtime 中有
category_t
结构体与之对应,而扩展则没有; - 扩展是编译时决议,分类是运行时决议,分类在运行加载阶段才载入方法列表中;
注意:分类是装饰器模式。用分类扩展的好处是:对父类的扩展可以直接作用于其所有的衍生类。
一、数据结构
1.1 分类的定义
分类的数据结构是category_t
结构体。包含了分类名称name
,分类所扩展的类cls
,分类实现的实例方法列表instanceMethods
,分类实现的类方法列表classMethods
,分类遵循的协议列表protocols
,分类定义的属性列表instanceProperties
。
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;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
复制代码
1.2 分类列表
类并不包含像分类列表这样的数据结构,category_t
结构体只是为了在编译阶段记录开发者定义的分类,并将其保存到特定的容器中。但是程序本身则需要保存分类列表,因为加载程序时,需要按照容器内记录的分类信息依次加载分类。保存应用定义的所有分类的容器是category_list
,也是locstamped_category_list_t
的别名。locstamped_category_list_t
是顺序表容器,元素为locstamped_category_t
结构体。locstamped_category_t
结构体包含指向category_t
结构体的cat
成员。
// 分类列表category_list是locstamped_category_list_t的别名
typedef locstamped_category_list_t category_list;
// 分类列表中的元素的类型,包含指向分类的指针
struct locstamped_category_t {
category_t *cat;
struct header_info *hi; // 先忽略
};
// locstamped_category_list_t 数组容器是实现了分类列表
struct locstamped_category_list_t {
uint32_t count;
#if __LP64__
uint32_t reserved;
#endif
locstamped_category_t list[0];
};
复制代码
二、分类加载
分类的加载是在应用的加载阶段,当应用完成类的基本信息(编译时决议的信息)加载后,需要将类的所有分类中的定义属性列表、方法列表等元素也添加到类的class_rw_t
中。
分类加载的核心代码在_read_images(...)
逻辑中。其中的关键环节包括:1、调用addUnattachedCategoryForClass(...)
将分类添加到类的 未处理分类列表;2、调用remethodizeClass(...)
根据分类重构类的方法列表,两个处理成对存在。前者收集所有未加载的分类列表,后者将未加载的分类列表中的方法列表、属性列表等信息加载到类中。
void _read_images(header_info **hList, uint32_t hCount)
{
#define EACH_HEADER \
hIndex = 0; \
crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \
hIndex++
...
// 加载分类
for (EACH_HEADER) {
// 获取分类列表,分类列表的顺序是后编译的分类在前(由attachCategories中分类列表的遍历顺序,
// 结合attachList的行为特征 以及 类的不同分类定义的同名方法的调用优先级,共同推断出)
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// 类为空则丢弃该分类的所有信息
catlist[i] = nil;
continue;
}
// 首先将分类添加到类的未处理分类
// 然后处理逐个类的未处理分类,重构类的属性列表、方法列表
// 类的处理
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
// 必须保证 class realizing 已完成,因为`class_ro_t`信息固定后
// 才能开始配置`class_rw_t`中的信息
remethodizeClass(cls);
classExists = YES;
}
}
// 元类的处理
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
...
}
复制代码
2.1 addUnattachedCategoryForClass
尚未将方法列表、属性列表等信息添加到所扩展类的class_rw_t
中 的分类,统一保存在一个静态的NXMapTable
类的哈希表中。该哈希表以Class
作为关键字,category_list
为值。通过unattachedCategories()
获取该哈希表。(category_list *)NXMapGet(unattachedCategories(), cls)
表示获取类cls
的未处理分类列表,需要对cls
添加未处理分类时,将其添加到类的未处理分类列表的结尾。
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// 不可用cat->cls替代,因为cls可能是cat->cls->isa(操作类方法时)
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// 将分类添加到list的末尾
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
runtimeLock.assertWriting();
static NXMapTable *category_map = nil;
if (category_map) return category_map;
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
复制代码
2.2 remethodizeClass
remethodizeClass
用于处理 2.1 中生成的unattachedCategories
哈希表中的所有分类 ,将分类扩展的方法、属性、协议,逐一添加到类的方法列表、属性列表、协议列表中。类的方法列表二维数组容器中后编译的分类的method_list_t
在前,先编译的分类的method_list_t
在后。在方法列表中查询方法时是以从开头到结尾的顺序遍历找到第一个匹配的方法返回。因此,响应方法时,后编译的分类的方法的优先级,高于先编译的分类的同名方法。
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
// 将类的未处理分类列表从哈希表中推出
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
runtimeLock.assertWriting();
return (category_list *)NXMapRemove(unattachedCategories(), cls);
}
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// 注意分类列表的遍历是从后向前的,因此方法列表二维数组容器中,元素对应的分类的顺序
// 和cats是一致的。
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
// 添加分类的方法列表 到类
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
// 添加分类的属性列表 到类
rw->properties.attachLists(proplists, propcount);
free(proplists);
// 添加分类的协议列表 到类
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
复制代码
三、总结
-
在应用载入阶段,将代码中定义的分类封装成
category_t
结构体保存,将所有分类信息收集到unattachedCategories
哈希表中,用于记录 尚未将方法列表、属性列表等信息添加到所扩展类的class_rw_t
中 的分类。哈希表以分类所扩展的类(Class)为关键字,扩展了该类的所有分类组成的分类列表为值,分类列表的数据结构是category_list
结构体; -
完成收集
unattachedCategories
哈希表后,将unattachedCategories
中所有分类的方法列表、属性列表、协议列表添加到 所扩展类的class_rw_t
中; -
后编译的分类的方法的优先级,高于先编译的分类的同名方法。即在类的方法列表二维数组容器中,后编译的分类的
method_list_t
在前,先编译的分类的method_list_t
在后,类的基础方法列表在最末尾; -
下一篇介绍 runtime 实现面向对象的一些具体实现细节;