Category

Category的底层结构

分类底层结构定义的如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//objc-runtime-new.h

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;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;

method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}

property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

项目中每定义一个分类,底层都会增加一个category_t对象。

Category的加载过程

Category源码阅读顺序:

  1. objc-os.mm (runtime入口)
    • _objc_init (runtime初始化)
    • map_images
    • map_images_nolock
  2. objc-runtime-new.mm
    • _read_images
    • remethodizeClass
    • attachCategories
    • attachLists
    • realloc、memmove、 memcpy

category的加载过程:

  1. 通过runtime加载类的所有分类
  2. 将所有分类的方法,属性,协议分别合并到一个数组
  3. 将合并后的分类数据插入到类原来到数据之前

由源码可见,对同名方法而言,会优先调用分类中的方法。如果多个分类中包含同名方法,则会调用最后参与编译的分类中的方法。

摘录源码中核心的attachCategories实现如下(objc4-756.2):

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
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);

bool isMeta = cls->isMetaClass();

// fixme rearrange to remove these intermediate allocations
//方法二维数组
//[[method_t,method_t],[method_t,method_t,method_t]]
//二维数组中的一个元素(数组)存放一个分类中的方法列表
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;
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, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}

protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//取出(元)类对象中的数据(class_rw_t)
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和Extension的区别

  • 类扩展中的信息在编译时会合并到类信息中。
  • 分类中的信息在运行时才会合并到类信息中。