-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathobjc-loadmethod.mm
431 lines (358 loc) · 16.6 KB
/
objc-loadmethod.mm
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
* Copyright (c) 2004-2006 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/***********************************************************************
* objc-loadmethod.m
* Support for +load methods.
**********************************************************************/
#include "objc-loadmethod.h"
#include "objc-private.h"
typedef void(*load_method_t)(id, SEL);
struct loadable_class {
Class cls; // may be nil
IMP method;
};
struct loadable_category {
Category cat; // may be nil
IMP method;
};
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
// 数组容器,记录包含load方法的所有类的信息
static struct loadable_class *loadable_classes = nil;
//数组内存中存在冗余空间,用loadable_classes_used实际保存的单元数量
static int loadable_classes_used = 0;
//数组内存中存在冗余空间,因此用loadable_classes_allocated记录分配的单元数量
static int loadable_classes_allocated = 0;
// List of categories that need +load called (pending parent class +load)
// 数组容器,记录包含load方法的所有分类的信息
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
/**
loadable_classes_used记录loadable_classes实际保存的loadable_class结构体数量
loadable_classes_allocated记录为数组分配的用于保存loadable_class结构体的内存单元数量
*/
// loadable_classes数组扩容,因此loadable_classes数组中是存在冗余空间的,这是loadable_classes_allocated存在原因
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
// ------- 将分类类添加到loadable_categories数组 ------- //
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
// loadable_categories数组扩容
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
/***********************************************************************
* remove_class_from_loadable_list
* Class cls may have been loadable before, but it is now no longer
* loadable (because its image is being unmapped).
**********************************************************************/
void remove_class_from_loadable_list(Class cls)
{
loadMethodLock.assertLocked();
if (loadable_classes) {
int i;
for (i = 0; i < loadable_classes_used; i++) {
if (loadable_classes[i].cls == cls) {
loadable_classes[i].cls = nil;
if (PrintLoading) {
_objc_inform("LOAD: class '%s' unscheduled for +load",
cls->nameForLogging());
}
return;
}
}
}
}
/***********************************************************************
* remove_category_from_loadable_list
* Category cat may have been loadable before, but it is now no longer
* loadable (because its image is being unmapped).
**********************************************************************/
void remove_category_from_loadable_list(Category cat)
{
loadMethodLock.assertLocked();
if (loadable_categories) {
int i;
for (i = 0; i < loadable_categories_used; i++) {
if (loadable_categories[i].cat == cat) {
loadable_categories[i].cat = nil;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' unscheduled for +load",
_category_getClassName(cat),
_category_getName(cat));
}
return;
}
}
}
}
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
/**
由于在prepare_load_methods已经确定了类的load方法执行顺序,因此call_class_loads(void)仅需简单迭代执行loadable_class中的load方法即可。处理过程大致如下:
.将局部变量classes指向loadable_classes,将loadable_classes指向nil。classes表示本次需要执行的所有类的load方法,为旧容器。loadable_classes表示本次执行的类的load方法中动态载入的所有新类的load方法,为新容器;
.遍历classes中所有loadable_class结构体,执行其method所指向的load方法。遍历classes时,若load方法中载入了新的类的load方法,则又会被收集于loadable_classes所指向的新容器中;
.释放classes局部变量所指向的旧容器内存空间;
*/
int i;
// Detach(分离) current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
// loadable_classes指向nil
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
// 执行load方法。load方法可能包含动态加载镜像的逻辑,此时loadable_classes则会指向
// 新的容器来收集动态加载镜像中的load方法
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes); // 释放旧容器
}
/***********************************************************************
* call_category_loads
* Call some pending category +load methods.
* The parent class of the +load-implementing categories has all of
* its categories attached, in case some are lazily waiting for +initalize.
* Don't call +load unless the parent class is connected.
* If new categories become loadable, +load is NOT called, and they
* are added to the end of the loadable list, and we return TRUE.
* Return FALSE if no new categories became loadable.
*
* Called only by call_load_methods().
**********************************************************************/
static bool call_category_loads(void)
{
/**
.局部变量cats指向loadable_categories表示旧容器。loadable_categories指向nil表示新容器;
.遍历旧容器中的所有loadable_category结构体,若loadable_category的cls成员非空且可加载,则执行method成员指向的load方法,并把cat成员置nil;
.用cats收集 旧容器中未执行load方法的所有分类(判断cat成员非空);
.用cats收集 执行旧容器的load方法过程中动态载入的所有分类;
.若cats保存的loadable_category结构体数量大于0,则设置loadable_categories指向cats所指向的内存空间;反之loadable_categories置nil。
*/
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
// 局部变量cats指向loadable_categories表示旧容器。
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
// loadable_categories指向nil表示新容器;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
// 遍历旧容器中的所有loadable_category结构体
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
// 若loadable_category的cls成员非空且可加载,则执行method成员指向的load方法,并把cat成员置nil
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
// 收集上面for循环未执行load方法的所有分类,其中包含了load方法中可能存在动态加载
// 镜像时载入的分类的load方法,这些load方法不能立刻执行,需要其扩展类的load方法
// 执行完毕后才能执行。
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
// loadable_categories旧容器中尚未执行load方法的loadable_category结构体数量,这些
// loadable_category均保留在loadable_categories新容器
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
// 若loadable_categories_used大于0,说明在执行分类load方法时收集到新的分类load方法
new_categories_added = (loadable_categories_used > 0);
// 将新收集的分类load方法添加到loadable_categories新容器
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
// 释放旧loadable_categories容器
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
// 赋值新loadable_categories容器
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
void call_load_methods(void)
{
/**
从loadable_classes容器及loadable_categories容器中推出类和分类,依次调用load方法。
call_load_methods(void)代码的逻辑比较怪异,
在do-while循环内部的while循环明明已经判断loadable_classes_used <= 0,为什么在do-while还要判断loadable_classes_used > 0进入下一次迭代?
这是因为类、分类的load方法中,均可能存在动态加载镜像文件的逻辑,从而引入新的类、分类的load方法。do-while循环内部,执行类的load方法使用了一个while循环,而执行分类的load方法则只调用了一次,这是因为分类load方法必须等待其扩展类的load方法执行完毕才能执行,因此需要立即进入下一次迭代以执行扩展类的load方法。
*/
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
// 1. 遍历并执行类的所有可调用的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
// 2. 执行分类的load方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
// 3. 循环直到类及分类的所有load方法均被执行
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}