声明:本文仅供记忆使用,并不适合新手小白观看
category应用场景
- 把类的不同实现方法分开到不同的文件里。
- 声明私有方法。
- 模拟多继承。
- 将
framework私有方法公开化。
category的实质
- Category 的本质就是 _category_t 结构体 类型
1 | struct category_t { |
Category的大致调用过程
-
通过Runtime加载某个类的所有Category数据
-
把所有Category的方法、属性、协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面
-
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
_objc_init
map_images
map_images_nolock_read_images
remethodizeClass 重建类的方法列表
attachCategories 为类添加未依附的分类
attachLists
realloc、memmove、 memcpy
Category的核心方法
-
_read_images 方法,忽略 _read_images 方法中其他与本文无关的代码,得到如下代码:
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// 获取镜像中的分类数组
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 遍历分类数组
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
// 处理这个分类
// 首先,使用目标类注册当前分类
// 然后,如果实现了这个类,重建类的方法列表
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}主要用到了两个方法:
addUnattachedCategoryForClass(cat, cls, hi);为类添加未依附的分类
remethodizeClass(cls);重建类的方法列表
通过这两个方法达到了两个目的:
把 Category(分类) 的对象方法、协议、属性添加到类上;
把 Category(分类) 的类方法、协议添加到类的 metaclass 上。 -
addUnattachedCategoryForClass(cat, cls, hi);方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20static void addUnattachedCategoryForClass(category_t *cat, Class cls, header_info *catHeader) {
runtimeLock.assertLocked();
// 取得存储所有未依附分类的列表:cats
NXMapTable *cats = unattachedCategories();
category_list *list;
// 从 cats 列表中找到 cls 对应的未依附分类的列表: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));
}
// 将新增的分类 cat 添加 list 中
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
// 将新生成的 list 添加重新插入 cats 中,会覆盖旧的 list
NXMapInsert(cats, cls, list);
}addUnattachedCategoryForClass(cat, cls, hi);的执行过程可以参考代码注释。执行完这个方法之后,系统会将当前分类 cat 放到该类 cls 对应的未依附分类的列表 list 中。这句话有点拗口,简而言之,就是:把类和分类做了一个关联映射。实际上真正起到添加加载作用的是下边的
remethodizeClass(cls);方法。 -
remethodizeClass(cls); 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// 取得 cls 类的未依附分类的列表:cats
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
// 将未依附分类的列表 cats 附加到 cls 类上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}remethodizeClass(cls);方法主要就做了一件事:调用attachCategories(cls, cats, true);方法将未依附分类的列表 cats 附加到 cls 类上。所以,我们就再来看看attachCategories(cls, cats, true);方法。 -
attachCategories(cls, cats, true); 方法
我发誓这是本文中加载 Category(分类)过程的最后一段代码。不过也是最为核心的一段代码。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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69static 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));
// Count backwards through cats to get newest categories first
int mcount = 0; // 记录方法的数量
int propcount = 0; // 记录属性的数量
int protocount = 0; // 记录协议的数量
int i = cats->count; // 从分类数组最后开始遍历,保证先取的是最新的分类
bool fromBundle = NO; // 记录是否是从 bundle 中取的
while (i--) { // 从后往前依次遍历
auto& entry = cats->list[i]; // 取出当前分类
// 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; // 将方法列表放入 mlists 方法列表数组中
fromBundle |= entry.hi->isBundle(); // 分类的头部信息中存储了是否是 bundle,将其记住
}
// 取出分类中的属性列表,如果是元类,取得的是 nil
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// 取出分类中遵循的协议列表
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 取出当前类 cls 的 class_rw_t 数据
auto rw = cls->data();
// 存储方法、属性、协议数组到 rw 中
// 准备方法列表 mlists 中的方法
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 将新方法列表添加到 rw 中的方法列表中
rw->methods.attachLists(mlists, mcount);
// 释放方法列表 mlists
free(mlists);
// 清除 cls 的缓存列表
if (flush_caches && mcount > 0) flushCaches(cls);
// 将新属性列表添加到 rw 中的属性列表中
rw->properties.attachLists(proplists, propcount);
// 释放属性列表
free(proplists);
// 将新协议列表添加到 rw 中的协议列表中
rw->protocols.attachLists(protolists, protocount);
// 释放协议列表
free(protolists);
}从
attachCategories(cls, cats, true);方法的注释中可以看出这个方法就是存储分类的方法、属性、协议的核心代码。但是需要注意一些细节问题:
Category(分类)的方法、属性、协议只是添加到原有类上,并没有将原有类的方法、属性、协议进行完全替换。
举个例子说明就是:假设原有类拥有 MethodA方法,分类也拥有 MethodA 方法,那么加载完分类之后,类的方法列表中会拥有两个 MethodA方法。
Category(分类)的方法、属性、协议会被添加到原有类的方法列表、属性列表、协议列表的最前面,而原有类的方法、属性、协议则被移动到了列表后面。
因为在运行时查找方法的时候是顺着方法列表的顺序依次查找的,所以 Category(分类)的方法会先被搜索到,然后直接执行,而原有类的方法则不被执行。这也是 Category(分类)中的方法会覆盖掉原有类的方法的最直接原因。