底层结构

1
2
3
4
5
6
7
8
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;
};

加载处理过程

  • 通过Runtime某个类的所有Category数据
  • 把所有Category的方法,属性,协议数据,合并到一个在数组中
    • 后面参与编译的Category数据,会在数组的前面
  • 将合并后的分类数据(方法,属性,协议),插入到类原来数据的前面
  • 源码解读顺序
    • objc-os.mm
      • _objc_init
      • map_images
      • map_images_nolock
    • objc-runtim-new.mm
      • _read_images
      • remethodizeClass
      • attachCategories
      • attachLists
        • realloc, memmove, memcpy

部分源码分析

objc-os.mm文件

_objc_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

// map_images
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

map_images_nolock

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
/***********************************************************************
* map_images_nolock
* Process the given images which are being mapped in by dyld.
* All class registration and fixups are performed (or deferred pending
* discovery of missing superclasses etc), and +load methods are called.
*
* info[] is in bottom-up order i.e. libobjc will be earlier in the
* array than any library that links to libobjc.
*
* Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images.
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif

void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// 省去其他代码...

if (hCount > 0) {

//_read_images
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

// 省去其他代码...
}

objc-runtime-new.mm文件

_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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// 省去其他代码...

// 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
|| (hasClassProperties && 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);
}
}
// 省去其他代码...

remethodizeClass

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
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;

runtimeLock.assertWriting();

isMeta = cls->isMetaClass();

// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}

//
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}

attachCategories

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
69
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_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;
}
}

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);
}

objc-runtime-new.h

attachLists

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
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;

if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;

//先进行内存挪动,array()->lists,原来的方法列表
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));

//再进行,copy操作到挪动后腾出的位置,addedLists,原有分类的方法列表
memcpy(array()->lists,
addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}

高频试题

  • Category的使用场景?
    • 为原有类添加对象方法和类方法
    • 拆分模块,将原来一个大的模块,拆分成多个小模块,方便维护和管理
    • 添加关联对象
  • Cateory的实现原理
    • Category编译之后的底层结构是 struct category_t,里面存储着分类 的对象方法,属性,协议信息
    • 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类对象)
  • Cateogyr和Class Extension的区别是什么?
    • Class Extension在编译的时候,它的数据就已经包含在类信息中
    • Category是在运行时,才会将数据合并到类信息中

参考