RunTime解析--Category(分类)实现

Runtime

在日常的开发中,我们经常用到分类,用来给一个类添加扩展,包括对象方法、类方法、当然我们还可以通过关联对象的方式给分类添属性。但是这中间的过程是如何实现的呢?下面我们来做一个详细的解释。

结构category_t

我们先看下分类的结构:

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
// 分类
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;
// methodsForMeta 返回类方法列表或者对象方法列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
// 属性列表返回方法
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从上面我们看到,一个分类的结构体中,包含我们最关心的几个结构体:instanceMethods,classMethods,protocols,instanceProperties,而对于_classProperties目前好像大多数文章在讲解时都将他选择性的忽略了,我们在本篇文章结束后在继续看下这个属性到底是什么有什么作用!

从上面的结构我们看出:分类是独立于原始类存在的。因此对于分类来说也肯定是有一块单独的内存空间来存放分类,那么到底是怎么存放的呢?我们下面来看下

分类的存储

我们先用CLang命令看下分类的结构和实现,我们新建一个类PrimaryObject以及他的分类PrimaryObject (Demo)

.h

1
2
3
4
5
6
7
8
9
10
11
@interface PrimaryObject : NSObject

- (void)test;

@end


@interface PrimaryObject (Demo)

@end

.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implementation PrimaryObject

- (void)test {
NSLog(@"PrimaryObject---test");
}

@end



@implementation PrimaryObject (Demo)

- (void)test {
NSLog(@"PrimaryObject-Demo---test");
}

@end

然后我们对.m文件使用clang命令看下生成的cpp结果,具体命令如下

1
clang -rewrite-objc MyClass.m

此时会在当前目录下生成一个PrimaryObject.cpp文件,我们来看下这个文件,我们可以通过我们的关键词定位到下面这段代码

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
70
71
72
73
74
75
76
77
78
79
// 对象方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = {
// 对应结构体中的entsize
sizeof(_objc_method),
// 对应结构体中的method_count
1,
// 对应结构中的_objc_method 方法名 参数
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_PrimaryObject_Demo_test}}
};

// 类方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = {
// 对应结构体中的entsize 与对象方法相同
sizeof(_objc_method),
// 对应结构体中的method_count
1,
// 对应结构中的_objc_method 方法名 参数
{{(struct objc_selector *)"classMethod", "v16@0:8", (void *)_C_PrimaryObject_Demo_classMethod}}
};

// 属性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) = {
// entsize
sizeof(_prop_t),
// count_of_properties
1,
// _prop_t
{{"demoCategoryArray","T@\"NSArray\",&,N"}}
};

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_PrimaryObject;

// 一个静态分类 结构体 名字为_OBJC_$_CATEGORY+分类名
static struct _category_t _OBJC_$_CATEGORY_PrimaryObject_$_Demo __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
// 类名
"PrimaryObject",
0, // &OBJC_CLASS_$_PrimaryObject,
// 对象方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_PrimaryObject_$_Demo,
// 类方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_PrimaryObject_$_Demo,
0,
// 属性列表
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_PrimaryObject_$_Demo,
};

//
static void OBJC_CATEGORY_SETUP_$_PrimaryObject_$_Demo(void ) {
_OBJC_$_CATEGORY_PrimaryObject_$_Demo.cls = &OBJC_CLASS_$_PrimaryObject;
}

#pragma section(".objc_inithooks$B", long, read, write)

__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_PrimaryObject_$_Demo,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_PrimaryObject,
};

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_PrimaryObject_$_Demo,
};

static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

首先编译器生成了实例方法列表_OBJC_$_CATEGORY_INSTANCE_METHODS_PrimaryObject_$_Demo和类方法列表_OBJC_$_CATEGORY_CLASS_METHODS_PrimaryObject_$_Demo以及属性列表_OBJC_$_PROP_LIST_PrimaryObject_$_Demo。三者的命名都遵循了公共前缀+类名+category名字的命名方式,而且实例方法列表和类方法列表里面填充的正是我们在Demo这个category里面写的方法classMethodclassMethod,而属性列表里面填充的也正是我们在Demo里添加的demoCategoryArray属性。还有一个需要注意到的事实就是category的名字用来给各种列表以及后面的category结构体本身命名,而且有static来修饰,所以在同一个编译单元里我们的category名不能重复,否则会出现编译错误。

其次,编译器生成了category本身_OBJC_$_CATEGORY_PrimaryObject_$_Demo,并用前面生成的列表来初始化category本身。

最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$,在DATA段下的__objc_classlist section里保存了一个大小为1的_category_t数组L_OBJC_LABEL_CLASS_

从上面Clang的代码中我们看到,分类的结构是独立于主类存在的,那么分类与主类是怎么关联的呢?分类又是如何加载的呢?

分类的加载

我们首先看下rumtime的入口方法

_objc_init

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
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
// 读取runtime的环境变量,如果需要则打印出来
environ_init();

tls_init();
//运行c++ 构造函数
static_init();
//lock 初始化 暂时是空的函数
lock_init();
//初始化系统异常操作
exception_init();
/*
仅供objc运行时使用,注册在映射、取消映射和初始化objc映像调用的处理程序。dyld将使用包含objc-image-info回调给`mapped`.
这些dylibs将自动引用计数,因此objc将不再需要调用dlopen()防止未加载。
在调用_dyld_objc_notify_register()期间,dyld将调用 `mapped` 在已经加载好 images,稍后dlopen()。
在调动init的时候也会调用`mapped`,在dyld调用的时候,也会调用init函数

在调用任何images +load方法时候
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

出去上面的init方法我们看到,dyld的朱啊哟做操是调用了_dyld_objc_notify_register方法,我们接着看下这个方法做了什么参数有代表什么意思。

_dyld_objc_notify_register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 仅供objc运行时使用
// Note: only for use by objc runtime
// 当objc镜像在映射、取消映射、和初始化时注册的事件会被调
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld会回调给将mapped函数一组包含objc-image-info段
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// 这些dylibs 将自动引用计数,因此objc将不再需要调用dlopen()防止未加载
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// 在调用_dyld_objc_notify_register()期间 dyld将调用 `mapped` 在已经加载好 images,稍后dlopen()。
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// 在调动init的时候也会调用`mapped`,在dyld调用的时候,也会调用init函数
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// 在调用任何images +load方法时候
// initializers in that image. This is when objc calls any +load methods in that image.
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);

总的来说上面这个方法的作用是:向dyld注册监听Mach-O中OC相关section被加载入\载出内存的事件,而具体的事件为:

  • _dyld_objc_notify_mapped 当dyld已将images加载入内存时回调。调用map_images方法
  • _dyld_objc_notify_init 当dyld初始化image后。OC调用类的+load方法,就是在这时进行的
  • _dyld_objc_notify_unmapped 当dyld将images移除内存时。调用unmap_image方法

我们先来看下第一个方法,上面我们知道这个方法会在dyld将image加到内存中后调用map_images方法:

map_images

1
2
3
4
5
6
7
8
// dyld将image加到内存中后调用
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}

我们重点看下map_images_nolock方法,因为这里我们主要关注的是分类的加载因此与分类加载无关的代码我们暂时先屏蔽不看

map_images_nolock

1
2
3
4
5
6
7
8
9
10
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// 当头文件个数大于0调用_read_images方法
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

}

map_images_nolock方法中主要是调用了_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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// 
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
TimeLogger ts(PrintImageTimes);

runtimeLock.assertLocked();

#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++

if (!doneOnce) {
doneOnce = YES;

if (DisableTaggedPointers) {
disableTaggedPointers();
}

initializeTaggedPointerObfuscator();

if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}

// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);

ts.log("IMAGE TIMES: first time tasks");
}


// Discover classes. Fix up unresolved future classes. Mark bundle classes.

for (EACH_HEADER) {
classref_t *classlist = _getObjc2ClassList(hi, &count);

if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}

bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();

for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}

ts.log("IMAGE TIMES: discover classes");

// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.

if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}

ts.log("IMAGE TIMES: remap classes");

// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;

bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}

ts.log("IMAGE TIMES: fix up selector references");

#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;

if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}

ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();

protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}

ts.log("IMAGE TIMES: discover protocols");

// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}

ts.log("IMAGE TIMES: fix up @protocol references");

// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;

// hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif

addClassTableEntry(cls);
realizeClass(cls);
}
}

ts.log("IMAGE TIMES: realize non-lazy classes");

// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
realizeClass(resolvedFutureClasses[i]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}

ts.log("IMAGE TIMES: realize future classes");

// Discover categories.
// #define EACH_HEADER hIndex = 0; hIndex < hCount && (hi = hList[hIndex]); hIndex++
for (EACH_HEADER) {
//GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
// 从__objc_catlist获取方法列表
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);

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;
// 如果这个类的instanceMethods、protocols、instanceProperties有值
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);
}
}
}
}

ts.log("IMAGE TIMES: discover categories");

// 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()

if (DebugNonFragileIvars) {
realizeAllClasses();
}

#undef EACH_HEADER
}

在这段代码中出现频次比较高的是EACH_HEADER显然这是一个for循环中的条件,我们来看下每次循环都做了什么

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
for (EACH_HEADER) {
classref_t *classlist = _getObjc2ClassList(hi, &count);
}

for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
}

for (EACH_HEADER) {
SEL *sels = _getObjc2SelectorRefs(hi, &count);
}

for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
}
for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
}

for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
}
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
}
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
}

经过这样的简化后,我们在通过这些循环中调用的方法来判断这些循环中到底做了什么,在找这些方法的时候我们发现这些方法实际上在runtime中有单独的定义的地方

1
2
3
4
5
6
7
8
9
10
11
12
//      function name                 content type     section name
GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs");
GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs");
GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs");
GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs");
GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist");
GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs");
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");

这实际上是rumtime是根据Mach-O各个section的信息来初始化其自身,结合上面我们说过分类的结构体和分类中的对象方法被添加到__DATA__下的__objc_catlist中,类方法被添加到__objc_classlist中,而属性则是被添加到__objc_const中。

下面我们先来看下__objc_catlist,同时我们定位到对应的for循环

__objc_catlist

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
    // 遍历所有分类
for (i = 0; i < count; i++) {
// 取出每一个分类
category_t *cat = catlist[i];
// 调用remapClass(cat->cls),并返回一个objc_class *对象cls。这一步的目的在于找到到category对应的类对象cls
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;
}

bool classExists = NO;
// 如果这个类的instanceMethods、protocols、instanceProperties有值
// 如果Category中有实例方法,协议,实例属性,会改写target class的结构
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
//
addUnattachedCategoryForClass(cat, cls, hi);
//修改class的method list结构
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" : "");
}
}

// 如果category中有类方法,协议,或类属性(目前OC版本不支持类属性), 会改写target class的元类结构
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);

// 修改class的method list结构
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}

不管是在分类中添加的是对象方法还是类方法,我们发现实际上调用的都是addUnattachedCategoryForClass方法,那么我们看下这个方法是如何实现的呢?

addUnattachedCategoryForClass

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
// 把类和category做一个关联映射
// category_t *cat 分类
// Class cls 原始类
// header_info *catHeader 分类头信息
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertLocked();

// DO NOT use cat->cls! cls may be cat->cls->isa instead
// 初始化一个分类表(这里是一个table)
NXMapTable *cats = unattachedCategories();
// 分类list
category_list *list;
// 获取原始类cls元有的分类表
list = (category_list *)NXMapGet(cats, cls);
// 如果之前没有 那么新建
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
// 如果之前已经有了 那么扩容list->count + 1
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// 将这个分类的信息放到与原始类相关的表中
list->list[list->count++] = (locstamped_category_t){cat, catHeader};

NXMapInsert(cats, cls, list);
}

将分类与原始类做了映射后,都调用remethodizeClass方法,我们看下这个方法做了什么

remethodizeClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;

runtimeLock.assertLocked();

isMeta = cls->isMetaClass();

// 取出还未被附加到class上的category list
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 将category附加到class上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}

上面这个方法,我们看到runtime会首先通过unattachedCategoriesForClass 取出还未被附加到class上的category list,然后调用attachCategories将这些category附加到class上。

所以主要的逻辑在attachCategories中,我们来看下这个方法

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
70
71
72
73
74
75
76
77
78
79
80
81
// 链接 分类中的 方法列表 属性和协议 到类中
// Class cls 原始类
// category_list *cats 分类列表
// bool flush_caches 是否需要刷新缓存
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_list_t *, property_list_t *, protocol_list_t *的数组空间,数组大小等于category的个数
//方法数组 二维数组 每个分类的方法是数组中的一个元素 每个分类可能有多个方法
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;
// i表示分类的个数
int i = cats->count;
bool fromBundle = NO;
//依次读取每一个category,将其methods,property,protocol添加到mlists,proplist,protolist中存储
while (i--) {
//取出某个分类
auto& entry = cats->list[i];
//取出分类 的 对象方法或者类方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
// 将方法列表加入都mlists中(注意mlist本身也是个数组) 因此mlists是一个二维数组
if (mlist) {
mlists[mcount++] = mlist; //mlists 接受所有分类方法
fromBundle |= entry.hi->isBundle();
}
//proplist 所有分类属性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);

if (proplist) {
proplists[propcount++] = proplist;
}

//proplist 所有协议方法
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}

// 取出class的data()数据,其实是class_rw_t * 指针,其对应结构体实例存储了class的基本信息
auto rw = cls->data();

prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//将category中的method 添加到class中
rw->methods.attachLists(mlists, mcount);
//释放数组
free(mlists);

// 如果需要,同时刷新class的method list cache
if (flush_caches && mcount > 0) flushCaches(cls);

// 将category的property添加到class中
rw->properties.attachLists(proplists, propcount);
//释放数组
free(proplists);


// 将category的protocol添加到class中
rw->protocols.attachLists(protolists, protocount);
//释放数组
free(protolists);
}

attachCategories对类和元类都生效,即不论我们是向类对象中添加对象方法还是向元类中添加类方法都是调用attachCategories方法,在方法中我们明确的看到方法属性协议分别添加到原始类的列表中,当然我们还发现不管是方法属性还是协议我们调用的都是attachLists方法,下面我们来进一步看看这个方法。

在分析attachLists方法之前我们先看下方法属性协议对应的类型:

1
2
3
4
5

struct class_rw_t {
method_array_t methods;//方法列表
property_array_t properties;//属性列表
protocol_array_t protocols;//协议列表

但实际上method_array_t,property_array_t,protocol_array_t都是对list_array_tt类型的包装。而attachLists刚好是list_array_tt中的方法。

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
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
 // 要添加的list 要添加的个数
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;

if (hasArray()) {
// 就list的个数
uint32_t oldCount = array()->count;
// 添加后list的个数
uint32_t newCount = oldCount + addedCount;
//分配内存 内存不够用了,需要扩容
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
//赋值count
array()->count = newCount;

// array()->lists:原来的方法列表向后移动 oldCount * sizeof(array()->lists[0]个长度
memmove(array()->lists + addedCount/*数组末尾*/, array()->lists/*数组*/,
oldCount * sizeof(array()->lists[0])/*移动的大小*/);

//空出来的 内存使用addedLists拷贝过去 大小是:addedCount * sizeof(array()->lists[0])
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
// 如果list为空 且要添加的个数为1 那么直接设置
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 如果要添加的个数大于1个
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)
// 将旧的数据放在了数组的addedCount的位置
// 前面addedCount个位置是为了给addedLists保留
array()->lists[addedCount] = oldList;

// 将addedLists拷贝到数组的0-addedCount的位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}

// memmove :内存移动。
/* __dst : 移动内存的目的地
* __src : 被移动的内存首地址
* __len : 被移动的内存长度
* 将__src的内存移动__len块内存到__dst中
*/
void *memmove(void *__dst, const void *__src, size_t __len);

// memcpy :内存拷贝。
/* __dst : 拷贝内存的拷贝目的地
* __src : 被拷贝的内存首地址
* __n : 被移动的内存长度
* 将__src的内存移动__n块内存到__dst中
*/
void *memcpy(void *__dst, const void *__src, size_t __n);

结合上图,我们发现将新的list插入到原有list的方式是:头部插入,结合方法调用的时候查找元类或者类对象的方法列表来调用,我们知道,分类的方法是会覆盖原始类的方法。这也从侧面证实了这一点。

上面的代码我们看到,无论是方法列表还是属性列表或者是协议列表我们都会添加到原始类对应的列表中,但是在实际使用中,我们都知道分类是无法添加属性的,这又是为什么呢?

注意:上面的这句描述实际上是有问题的,准确的描述应该是分类可以添加属性但是系统不会为这个属性动态添加setter和getter方法(以及创建一个以_开头的实例变量)。

AssociatedObject

不过runtime还是提供了关联属性的方法,让我们可以在分类中添加属性(实例变量+getter+setter),下面我们来看下关联属性的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}


void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}


void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}

在看这几个方法的具体实现之前,我们先看下policy参数的值

objc_AssociationPolicy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
/**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_ASSIGN = 0,
/**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
/**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
/**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401,
/**< Specifies that the associated object is copied.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403
};

下面我们依次看下这三个方法,首先_object_set_associative_reference设置关联属性。

_object_set_associative_reference

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
// 设置关联对象的值
// object 目标对象
// 关联对象的key
// 关联对象的值
// 关联策略
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
//获取new_value的值
// acquireValue根据policy== OBJC_ASSOCIATION_SETTER_COPY 或者OBJC_ASSOCIATION_SETTER_RETAIN
// 判断是要进行retain操作还是copy操作
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
//生成一个全局的 HashMap
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
//newValue存在 需要更新
if (new_value) {
// 遍历 hashMap是否有该obj
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 遍历
if (i != associations.end()) {
//这个对象有关联对象 注意这里取出的并不是某个对象 而是一个哈希表
ObjectAssociationMap *refs = i->second;
// 根据key找到对应的关联对象
ObjectAssociationMap::iterator j = refs->find(key);
// 遍历根据key的查找结果
if (j != refs->end()) {
// 如果找到了 更新值
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 如果找到最后仍然没有找到 那么新增插入
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
//如果这个对象之前就没有关联对象 那么新建
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// 如果传入的关联对象值为nil,则断开关联
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// 释放旧值
if (old_association.hasValue()) ReleaseValue()(old_association);
}

大概的流程是:

  • 根据关联的policy,调用id new_value = value ? acquireValue(value, policy) : nil; ,acquireValue 方法会根据poilcy是retain或copy,对value做引用+1操作或copy操作,并返回对应的new_value。(如果传入的value为nil,则返回nil,不做任何操作)
  • 获取到new_value 后,根据是否有new_value的值。如果 new_value 存在,则对象与目标对象关联。实质是存入到全局单例 AssociationsManager manager 的对象关联表中。 如果new_value 不存在,则释放掉之前目标对象及关联 key所存储的关联对象。实质是在 AssociationsManager 中删除掉关联对象。
  • 最后,释放掉之前以同样key存储的关联对象

从上面的代码中我们看到所有对象的关联属性实际上都存放在一个全局的静态哈希表中 static AssociationsHashMap *_map; 然后分别根据对象地址取出对象所有的关联属性,进而根据key去获取对应的关联对象。大致结构如下图:

接着我们在来看下获取的方法即_object_get_associative_reference方法:

_object_get_associative_reference

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
// 获取一个已关联的对象
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 可以把manager理解为一个单例 实际上是一个静态变量
AssociationsManager manager;
//初始化 _map = AssociationsHashMap
AssociationsHashMap &associations(manager.associations());
// 取反object 地址 作为accociative key
disguised_ptr_t disguised_object = DISGUISE(object);
// 在关联对象海西表中 找到与disguised_object关联的所有对象
AssociationsHashMap::iterator i = associations.find(disguised_object);

// 遍历这个哈希表
if (i != associations.end()) {
// 取出当前的这个关联对象
ObjectAssociationMap *refs = i->second;
// 根据key查找对应的关联对象 有可能存在多个
ObjectAssociationMap::iterator j = refs->find(key);
// 遍历查找结果
if (j != refs->end()) {
// 取出关联实体
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
// 如果policy = OBJC_ASSOCIATION_GETTER_AUTORELEASE
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}

结合上面图中的存储结构,get方法的流程为:

  • 根据对象地址获取该对象所有的关联属性
    AssociationsHashMap::iterator i = associations.find(disguised_object);

  • 根据关联对象的key获取到具体的关联对象并返回 同时根据该关联对象的策略 判断是否需要retain

紧接着我们再来看下释放的方法objc_removeAssociatedObjects,首先我们先搜索一下这个方法的调用时机,

objc_destructInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void *objc_destructInstance(id obj) 
{
if (obj) {
Class isa = obj->getIsa();

if (isa->hasCxxDtor()) {
object_cxxDestruct(obj);
}

if (isa->instancesHaveAssociatedObjects()) {
_object_remove_assocations(obj);
}

objc_clear_deallocating(obj);
}

return obj;
}

objc_destructInstance方法是对象在被释放时调用的,那么objc_removeAssociatedObjects的时机就是在对象被释放时,如果发现这个对象有关联对象那么就去释放这个对象的关联对象。

具体的释放方法如下:

_object_remove_assocations

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
// 对象释放时移除所有的关联属性
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());

if (associations.size() == 0) return;

disguised_ptr_t disguised_object = DISGUISE(object);
// 根据对象地址取出所有的关联对象
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 拷贝所有的关联关系
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
// 记录所有的关联对象到elements
elements.push_back(j->second);
}
// 删除refs 删除这个对象的所有关联对象
delete refs;
// 清除关联关系
associations.erase(i);
}
}
// elements数组中的所有元素执行ReleaseValue操作
for_each(elements.begin(), elements.end(), ReleaseValue());
}

删除关联对象的时候,我们实际上是将某个对象的所有关联对象放到一个数组中,先清除关联关系和,然后在对数组中的所有元素执行release操作。

通过上面的讲述,我们知道了分类方法是如何被添加到原始类的方法列表、属性列表、协议列表中的。而且添加的时机为map_images(参考前面讲述的_objc_init方法)。

那么我们接着看map_images方法执行后会继续执行load_images,unmap_image方法,

load_images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;

recursive_mutex_locker_t lock(loadMethodLock);

// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
//加载 class+load 和category+load方法
prepare_load_methods((const headerType *)mh);
}

// Call +load methods (without runtimeLock - re-entrant)
//执行 class+load 和category+load方法
call_load_methods();
}

我们看到load_images方法中主要是执行了call_load_methods方法,这也侧面证明在+load方法中我们可以使用关联对象,因为对应的属性和方法列表已经被添加到类中。

而对于call_load_methods方法:

call_load_methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 执行+load方法
void call_load_methods(void)
{
do {
//执行class+load直到完成
while (loadable_classes_used > 0) {
call_class_loads();
}
// 执行Category +load 一次
more_categories = call_category_loads();

// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
}

我们也看到,+load方法是先调用了原始类中的+load方法,再调用分类中的load方法。

总结

这篇文章我们主要介绍了分类的结构和分类是如何与主类关联的,同时分析了关联对象方法的具体实现。同时在介绍load_images方法时,我们又验证了+load方法的调用时机以及与分类方法链接到主类的执行顺序。