├── Article
└── Category:从底层原理研究到面试题分析.md
└── README.md
/Article/Category:从底层原理研究到面试题分析.md:
--------------------------------------------------------------------------------
1 | #
Category:从底层原理研究到面试题分析
2 |
3 | 
4 |
5 | > 本文将对category的源码进行比较全面的整理分析,最后结合一些面试题进行总结,希望对读者有所裨益。
6 | > GitHub Repo:[iOSDeepAnalyse](https://github.com/MisterBooo/iOSDeepAnalyse)
7 | > Follow: [MisterBooo · GitHub](https://github.com/MisterBooo)
8 | > Source: [Category:从底层原理研究到面试题分析](https://github.com/MisterBooo/iOSDeepAnalyse/blob/master/Article/Category:从底层原理研究到面试题分析.md)
9 |
10 | ### 目录
11 | * 1.Category源码分析
12 | * 2.load源码分析
13 | * 3.initialize源码分析
14 | * 4.load与initialize对比
15 | * 5.面试题分析
16 |
17 |
18 |
19 | ## 源码分析
20 |
21 | ### 1.源码阅读前的准备
22 | 本节代码基于以下的代码进行编译研究:
23 |
24 | ```
25 | @interface Person : NSObject
26 | - (void)instanceRun;
27 | + (void)methodRun;
28 | @property(nonatomic, copy) NSString *name;
29 | @end
30 | ```
31 |
32 | ```
33 | @interface Person (Eat)
34 | @property(nonatomic, assign) int age;
35 | - (void)instanceEat;
36 | + (void)methodEat;
37 | @end
38 |
39 | ```
40 |
41 | ```
42 | @interface Person (Drink)
43 | - (void)instanceEat;
44 | @property(nonatomic, copy) NSString *waters;
45 | @end
46 | ```
47 |
48 |
49 | ### 2.objc4中的源码
50 | 通过[objc4](https://opensource.apple.com/tarballs/objc4/)中的源码进行分析,可以在`objc-runtime-new.h`中找到`Category`的结构如下
51 |
52 | ```
53 | struct category_t {
54 | const char *name;
55 | classref_t cls;
56 | struct method_list_t *instanceMethods;
57 | struct method_list_t *classMethods;
58 | struct protocol_list_t *protocols;
59 | struct property_list_t *instanceProperties;
60 | // Fields below this point are not always present on disk.
61 | struct property_list_t *_classProperties;
62 |
63 | method_list_t *methodsForMeta(bool isMeta) {
64 | if (isMeta) return classMethods;
65 | else return instanceMethods;
66 | }
67 |
68 | property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
69 | };
70 | ```
71 | 不难发现在这个结构体重存储着对象方法、类方法、协议和属性。接下来我们来验证一下我们刚刚自己编写的`Person+Eat.m`这个分类在编译时是否是这种结构。
72 |
73 | 通过
74 |
75 | `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Eat.m`
76 |
77 | 命令将`Person+Eat.m`文件编译成`cpp`文件,以下的源码分析基于`Person+Eat.cpp`里面的代码。下面让我们开始窥探Category的底层结构吧~
78 |
79 | ### 2.Person+Eat.cpp源码
80 | 将`Person+Eat.cpp`的代码滑到底部部分,可以看见一个名为`_category_t`的结构体,这就是Category的底层结构
81 |
82 | ```
83 | struct _category_t {
84 | const char *name;
85 | struct _class_t *cls;
86 | const struct _method_list_t *instance_methods; // 对象方法列表
87 | const struct _method_list_t *class_methods;// 类方法列表
88 | const struct _protocol_list_t *protocols;// 协议列表
89 | const struct _prop_list_t *properties;// 属性列表
90 | };
91 | ```
92 | `Person+Eat.m`这个分类的结构也是符合`_category_t `这种形式的
93 |
94 | ```
95 | static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
96 | {
97 | "Person",
98 | 0, // &OBJC_CLASS_$_Person,
99 | (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, // 对象方法列表
100 | (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,// 类方法列表
101 | (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat, // 协议列表
102 | (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat, // 属性列表
103 | };
104 | ```
105 | 我们开始来分析上面这个结构体的内部成员,其中`Person `表示类名
106 | ##### 对象方法列表
107 | `_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat`是对象方法列表,在`Person+Eat.cpp`文件中可以找到`_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat`具体描述
108 |
109 | ```
110 | _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
111 | sizeof(_objc_method),
112 | 1,
113 | {{(struct objc_selector *)"instanceEat", "v16@0:8", (void *)_I_Person_Eat_instanceEat}}
114 | };
115 | ```
116 | `instanceEat `就是我们上述实现的`Person+Eat`分类里面的实例方法。
117 |
118 | ##### 类方法列表
119 | `_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat`是类方法列表,在`Person+Eat.cpp`中具体描述如下
120 |
121 | ```
122 | _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
123 | sizeof(_objc_method),
124 | 1,
125 | {{(struct objc_selector *)"classEat", "v16@0:8", (void *)_C_Person_Eat_classEat}}
126 | };
127 | ```
128 | ##### 协议列表
129 | `_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat`是协议列表,在`Person+Eat.cpp`中具体描述如下
130 |
131 | ```
132 | _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
133 | 2,
134 | &_OBJC_PROTOCOL_NSCopying,
135 | &_OBJC_PROTOCOL_NSCoding
136 | };
137 | ```
138 | ##### 属性列表
139 | `_OBJC_$_PROP_LIST_Person_$_Eat`是属性列表,在`Person+Eat.cpp`中具体描述如下
140 |
141 | ```
142 | _OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
143 | sizeof(_prop_t),
144 | 2,
145 | {{"weight","Ti,N"},
146 | {"height","Ti,N"}}
147 | };
148 | ```
149 | ### 3.Category的加载处理过程
150 | 通过上面的分析,我们验证了编写一个分类的时候,在编译期间,这个分类内部的确会有`category_t`这种数据结构,那么这种数据结构是如何作用到这个类的呢?分类的方法和类的方法调用的逻辑是怎么样的呢?我们接下来回到[objc4](https://opensource.apple.com/tarballs/objc4/)源码中进一步分析`Category `的加载处理过程来揭晓`Category `的神秘面纱。
151 |
152 | 我们按照如下函数的调用顺序,一步一步的研究`Category`的加载处理过程
153 |
154 | ```
155 | void _objc_init(void);
156 | └── void map_images(...);
157 | └── void map_images_nolock(...);
158 | └── void _read_images(...);
159 | └── void _read_images(...);
160 | └── static void remethodizeClass(Class cls);
161 | └──attachCategories(Class cls, category_list *cats, bool flush_caches);
162 | ```
163 | | 文件名 | 方法 |
164 | | --- | --- |
165 | | objc-os.mm | `_objc_init` |
166 | | objc-os.mm | `map_images` |
167 | | objc-os.mm | `map_images_nolock` |
168 | | objc-runtime-new.mm | `_read_images` |
169 | | objc-runtime-new.mm | `remethodizeClass`
170 | | objc-runtime-new.mm | `attachCategories`|
171 | | objc-runtime-new.mm | `attachLists`|
172 |
173 | 在[iOS 程序 main 函数之前发生了什么
174 | ](https://blog.sunnyxx.com/2014/08/30/objc-pre-main/)中提到,`_objc_init`这个函数是runtime的初始化函数,那我们从`_objc_init`这个函数开始进行分析。
175 |
176 | ```
177 | /***********************************************************************
178 | * _objc_init
179 | * Bootstrap initialization. Registers our image notifier with dyld.
180 | * Called by libSystem BEFORE library initialization time
181 | **********************************************************************/
182 |
183 | void _objc_init(void)
184 | {
185 | static bool initialized = false;
186 | if (initialized) return;
187 | initialized = true;
188 |
189 | // fixme defer initialization until an objc-using image is found?
190 | environ_init();
191 | tls_init();
192 | static_init();
193 | lock_init();
194 | exception_init();
195 |
196 | _dyld_objc_notify_register(&map_images, load_images, unmap_image);
197 | }
198 | ```
199 |
200 | 接着我们来到 `&map_images`读取资源(images这里代表资源模块),来到`map_images_nolock`函数中找到`_read_images`函数,在`_read_images`函数中我们找到与分类相关的代码
201 |
202 | ```
203 | // Discover categories.
204 | for (EACH_HEADER) {
205 | category_t **catlist =
206 | _getObjc2CategoryList(hi, &count);
207 | bool hasClassProperties = hi->info()->hasCategoryClassProperties();
208 |
209 | for (i = 0; i < count; i++) {
210 | category_t *cat = catlist[i];
211 | Class cls = remapClass(cat->cls);
212 |
213 | if (!cls) {
214 | catlist[i] = nil;
215 | if (PrintConnecting) {
216 | _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
217 | "missing weak-linked target class",
218 | cat->name, cat);
219 | }
220 | continue;
221 | }
222 | bool classExists = NO;
223 | if (cat->instanceMethods || cat->protocols
224 | || cat->instanceProperties)
225 | {
226 | addUnattachedCategoryForClass(cat, cls, hi);
227 | if (cls->isRealized()) {
228 | remethodizeClass(cls);
229 | classExists = YES;
230 | }
231 | if (PrintConnecting) {
232 | _objc_inform("CLASS: found category -%s(%s) %s",
233 | cls->nameForLogging(), cat->name,
234 | classExists ? "on existing class" : "");
235 | }
236 | }
237 |
238 | if (cat->classMethods || cat->protocols
239 | || (hasClassProperties && cat->_classProperties))
240 | {
241 | addUnattachedCategoryForClass(cat, cls->ISA(), hi);
242 | if (cls->ISA()->isRealized()) {
243 | remethodizeClass(cls->ISA());
244 | }
245 | if (PrintConnecting) {
246 | _objc_inform("CLASS: found category +%s(%s)",
247 | cls->nameForLogging(), cat->name);
248 | }
249 | }
250 | }
251 | }
252 |
253 | ```
254 | 在上面的代码中,主要做了以下的事情
255 |
256 | * 1.获取`category`列表`list`
257 | * 2.遍历`category list`中的每一个`category`
258 | * 3.获取`category`的`cls`,如果没有`cls`,则跳过(`continue `)这个继续获取下一个
259 | * 4.如果`cat`有实例方法、协议、属性,则调用`addUnattachedCategoryForClass `,同时如果`cls`有实现的话,就进一步调用`remethodizeClass `方法
260 | * 5.如果`cat`有类方法、协议,则调用`addUnattachedCategoryForClass `,同时如果`cls`的元类有实现的话,就进一步调用`remethodizeClass `方法
261 |
262 | 其中`4`,`5`两步的区别主要是`cls`是类对象还是元类对象的区别,我们接下来主要是看在第`4`步中的`addUnattachedCategoryForClass `和`remethodizeClass `方法。
263 |
264 | ##### addUnattachedCategoryForClass
265 | ```
266 | static void addUnattachedCategoryForClass(category_t *cat, Class cls,
267 | header_info *catHeader)
268 | {
269 | runtimeLock.assertWriting();
270 |
271 | // DO NOT use cat->cls! cls may be cat->cls->isa instead
272 | NXMapTable *cats = unattachedCategories();
273 | category_list *list;
274 |
275 | list = (category_list *)NXMapGet(cats, cls);
276 | if (!list) {
277 | list = (category_list *)
278 | calloc(sizeof(*list) + sizeof(list->list[0]), 1);
279 | } else {
280 | list = (category_list *)
281 | realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
282 | }
283 | list->list[list->count++] = (locstamped_category_t){cat, catHeader};
284 | NXMapInsert(cats, cls, list);
285 | }
286 | static NXMapTable *unattachedCategories(void)
287 | {
288 | runtimeLock.assertWriting();
289 | //全局对象
290 | static NXMapTable *category_map = nil;
291 | if (category_map) return category_map;
292 | // fixme initial map size
293 | category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
294 | return category_map;
295 | }
296 | ```
297 | 对上面的代码进行解读:
298 |
299 | * 1.通过`unattachedCategories()`函数生成一个全局对象`cats `
300 | * 2.我们从这个单例对象中查找`cls` ,获取一个`category_list` *`list`列表
301 | * 3.要是没有`list` 指针。那么我们就生成一个`category_list` 空间。
302 | * 4.要是有`list` 指针,那么就在该指针的基础上再分配出`category_list` 大小的空间
303 | * 5.在这新分配好的空间,将这个`cat` 和`catHeader` 写入。
304 | * 6.将数据插入到`cats` 中,`key` 是`cls`, 值是`list`
305 |
306 | ##### remethodizeClass
307 | ```
308 | static void remethodizeClass(Class cls)
309 | {
310 | //分类数组
311 | category_list *cats;
312 | bool isMeta;
313 |
314 | runtimeLock.assertWriting();
315 |
316 | isMeta = cls->isMetaClass();
317 |
318 | // Re-methodizing: check for more categories
319 | if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
320 | if (PrintConnecting) {
321 | _objc_inform("CLASS: attaching categories to class '%s' %s",
322 | cls->nameForLogging(), isMeta ? "(meta)" : "");
323 | }
324 |
325 | attachCategories(cls, cats, true /*flush caches*/);
326 | free(cats);
327 | }
328 | }
329 | ```
330 | 在`remethodizeClass `函数中将通过`attachCategories `函数我们的分类信息附加到该类中。
331 |
332 | ##### attachCategories
333 | ```
334 | //cls = [Person class]
335 | //cats = [category_t(Eat),category_t(Drink)]
336 | static void
337 | attachCategories(Class cls, category_list *cats, bool flush_caches)
338 | {
339 | if (!cats) return;
340 | if (PrintReplacedMethods) printReplacements(cls, cats);
341 |
342 | bool isMeta = cls->isMetaClass();
343 |
344 | // 重新分配内存
345 | method_list_t **mlists = (method_list_t **)
346 | malloc(cats->count * sizeof(*mlists));
347 | property_list_t **proplists = (property_list_t **)
348 | malloc(cats->count * sizeof(*proplists));
349 | protocol_list_t **protolists = (protocol_list_t **)
350 | malloc(cats->count * sizeof(*protolists));
351 |
352 | // Count backwards through cats to get newest categories first
353 | int mcount = 0;
354 | int propcount = 0;
355 | int protocount = 0;
356 | int i = cats->count;
357 | bool fromBundle = NO;
358 | while (i--) {
359 | auto& entry = cats->list[i];
360 |
361 | method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
362 | if (mlist) {
363 | mlists[mcount++] = mlist;
364 | fromBundle |= entry.hi->isBundle();
365 | }
366 |
367 | property_list_t *proplist =
368 | entry.cat->propertiesForMeta(isMeta, entry.hi);
369 | if (proplist) {
370 | proplists[propcount++] = proplist;
371 | }
372 |
373 | protocol_list_t *protolist = entry.cat->protocols;
374 | if (protolist) {
375 | protolists[protocount++] = protolist;
376 | }
377 | }
378 |
379 | auto rw = cls->data();
380 |
381 | prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
382 | rw->methods.attachLists(mlists, mcount);
383 | free(mlists);
384 | if (flush_caches && mcount > 0) flushCaches(cls);
385 |
386 | rw->properties.attachLists(proplists, propcount);
387 | free(proplists);
388 |
389 | rw->protocols.attachLists(protolists, protocount);
390 | free(protolists);
391 | }
392 | ```
393 | 对上面的代码进行解读(假设`cls`是类对象,元类对象分析同理):
394 |
395 | * 1.根据方法列表、属性列表、协议列表分配内存
396 | * 2.`cats `是这种数据结构:`[category_t(Eat),category_t(Drink),。。。]`,遍历`cats `,然后
397 | * 1.获取一个分类里面的所有对象方法,存储在`mlist `数组中,然后再将`mlist `数组添加到二维数组`mlists `中
398 | * 2.获取一个分类里面的所有协议,存储在`proplist `数组中,然后再将`proplist `数组添加到二维数组`proplists `中
399 | * 3.获取一个分类里面的所有属性,存储在`protolist `数组中,然后再将`protolist `数组添加到二维数组`protolists `中
400 |
401 | * 3.获取`cls` 的的`bits` 指针 `class_rw_t`,通过`attachLists`方法,将`mlists`附加到类对象方法列表中,将`proplists `附加到类对象的属性列表中,将`protolists `附加到类对象的协议列表中
402 |
403 | 其中`mlists `的数据结构如下,`proplists `与`protolists `同理:
404 |
405 | ```
406 | [
407 | [method_t,method_t],
408 | [method_t,method_t]
409 | ]
410 | ```
411 | ##### attachLists
412 |
413 | ```
414 | void attachLists(List* const * addedLists, uint32_t addedCount) {
415 | if (addedCount == 0) return;
416 |
417 | if (hasArray()) {
418 | // many lists -> many lists
419 | uint32_t oldCount = array()->count;
420 | uint32_t newCount = oldCount + addedCount;
421 | setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
422 | array()->count = newCount;
423 | memmove(array()->lists + addedCount, array()->lists,
424 | oldCount * sizeof(array()->lists[0]));
425 | memcpy(array()->lists, addedLists,
426 | addedCount * sizeof(array()->lists[0]));
427 | }
428 | else if (!list && addedCount == 1) {
429 | // 0 lists -> 1 list
430 | list = addedLists[0];
431 | }
432 | else {
433 | // 1 list -> many lists
434 | List* oldList = list;
435 | uint32_t oldCount = oldList ? 1 : 0;
436 | uint32_t newCount = oldCount + addedCount;
437 | setArray((array_t *)malloc(array_t::byteSize(newCount)));
438 | array()->count = newCount;
439 | if (oldList) array()->lists[addedCount] = oldList;
440 | memcpy(array()->lists, addedLists,
441 | addedCount * sizeof(array()->lists[0]));
442 | }
443 | }
444 | ```
445 | 在`attachLists `方法主要关注两个变量`array()->lists`和`addedLists `
446 |
447 | * **array()->lists**: 类对象原来的方法列表,属性列表,协议列表,比如Person中的那些方法等
448 | * **addedLists**:传入所有分类的方法列表,属性列表,协议列表,比如Person(Eat)、Person(Drink)中的那些方法等。
449 |
450 | 上面代码的作用就是通过`memmove `将原来的类找那个的方法、属性、协议列表分别进行后移,然后通过`memcpy `将传入的方法、属性、协议列表填充到开始的位置。
451 | 我们来总结一下这个过程:
452 |
453 | * 通过Runtime加载某个类的所有Category数据
454 |
455 | * 把所有Category的方法、属性、协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面
456 |
457 | * 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
458 |
459 |
460 | 我们可以用如下的动画来表示一下这个过程
461 |
462 |
463 | ----
464 |
465 | 
466 |
467 | ----
468 |
469 |
470 | [通过这个动画我们不难注意到以下两点:](https://tech.meituan.com/DiveIntoCategory.html)
471 |
472 | * 1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA.
473 | * 2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。
474 |
475 |
476 | **我们通过代码来验证一下上面两个注意点是否正确**
477 |
478 | 
479 |
480 | ## load与initialize
481 |
482 | > load方法与initialize方法的调用与一般普通方法的调用有所区别,因此笔者将其放在这一节一并分析进行想对比
483 |
484 | #### load源码分析
485 | 同样的,我们按照如下函数的调用顺序,一步一步的研究`load `的加载处理过程
486 |
487 | ```
488 | void _objc_init(void);
489 | └── void load_images(...);
490 | └── void call_load_methods(...);
491 | └── void call_class_loads(...);
492 | ```
493 | 我们直接从`load_images`方法进行分析
494 |
495 | ```
496 | void
497 | load_images(const char *path __unused, const struct mach_header *mh)
498 | {
499 | // Return without taking locks if there are no +load methods here.
500 | if (!hasLoadMethods((const headerType *)mh)) return;
501 |
502 | recursive_mutex_locker_t lock(loadMethodLock);
503 |
504 | // Discover load methods
505 | {
506 | rwlock_writer_t lock2(runtimeLock);
507 | prepare_load_methods((const headerType *)mh);
508 | }
509 |
510 | // Call +load methods (without runtimeLock - re-entrant)
511 | call_load_methods();
512 | }
513 | ```
514 | 在`load_images `方法中主要关注`prepare_load_methods `方法与`call_load_methods`方法
515 |
516 | ##### `prepare_load_methods `
517 |
518 | ```
519 | void prepare_load_methods(header_info *hi)
520 | {
521 | size_t count, i;
522 |
523 | rwlock_assert_writing(&runtimeLock);
524 |
525 | classref_t *classlist =
526 | _getObjc2NonlazyClassList(hi, &count);
527 | for (i = 0; i < count; i++) {
528 | schedule_class_load(remapClass(classlist[i]));
529 | }
530 |
531 | category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
532 | for (i = 0; i < count; i++) {
533 | category_t *cat = categorylist[i];
534 | Class cls = remapClass(cat->cls);
535 | if (!cls) continue; // category for ignored weak-linked class
536 | realizeClass(cls);
537 | assert(cls->ISA()->isRealized());
538 | add_category_to_loadable_list(cat);
539 | }
540 | }
541 | ```
542 |
543 | ```
544 | static void schedule_class_load(Class cls)
545 | {
546 | if (!cls) return;
547 | assert(cls->isRealized()); // _read_images should realize
548 |
549 | if (cls->data()->flags & RW_LOADED) return;
550 |
551 | // 确保父类优先的顺序
552 | schedule_class_load(cls->superclass);
553 |
554 | add_class_to_loadable_list(cls);
555 | cls->setInfo(RW_LOADED);
556 | }
557 | ```
558 |
559 | 顾名思义,这个函数的作用就是提前准备好满足 +load 方法调用条件的类和分类,以供接下来的调用。
560 | 然后在这个类中调用了`schedule_class_load(Class cls)`方法,并且在入参时对父类递归的调用了,确保父类优先的顺序。
561 |
562 | ##### `call_load_methods `
563 | 经过`prepare_load_methods `的准备,接下来`call_load_methods `就开始大显身手了。
564 |
565 | ```
566 | void call_load_methods(void)
567 | {
568 | static BOOL loading = NO;
569 | BOOL more_categories;
570 |
571 | recursive_mutex_assert_locked(&loadMethodLock);
572 |
573 | // Re-entrant calls do nothing; the outermost call will finish the job.
574 | if (loading) return;
575 | loading = YES;
576 |
577 | void *pool = objc_autoreleasePoolPush();
578 |
579 | do {
580 | // 1. Repeatedly call class +loads until there aren't any more
581 | while (loadable_classes_used > 0) {
582 | call_class_loads();
583 | }
584 |
585 | // 2. Call category +loads ONCE
586 | more_categories = call_category_loads();
587 |
588 | // 3. Run more +loads if there are classes OR more untried categories
589 | } while (loadable_classes_used > 0 || more_categories);
590 |
591 | objc_autoreleasePoolPop(pool);
592 |
593 | loading = NO;
594 | }
595 | ```
596 | 在`call_load_methods `中我们看`do`循环这个方法,它调用上一步准备好的类和分类中的 +load 方法,并且确保类优先于分类的顺序。
597 |
598 | ##### `call_class_loads `
599 | `call_class_loads `是`load`方法调用的核心方法
600 |
601 | ```
602 | static void call_class_loads(void)
603 | {
604 | int i;
605 |
606 | // Detach current loadable list.
607 | struct loadable_class *classes = loadable_classes;
608 | int used = loadable_classes_used;
609 | loadable_classes = nil;
610 | loadable_classes_allocated = 0;
611 | loadable_classes_used = 0;
612 |
613 | // Call all +loads for the detached list.
614 | for (i = 0; i < used; i++) {
615 | Class cls = classes[i].cls;
616 | load_method_t load_method = (load_method_t)classes[i].method;
617 | if (!cls) continue;
618 |
619 | if (PrintLoading) {
620 | _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
621 | }
622 | (*load_method)(cls, SEL_load);
623 | }
624 |
625 | // Destroy the detached list.
626 | if (classes) _free_internal(classes);
627 | }
628 | ```
629 | 这个函数的作用就是真正负责调用类的 `+load` 方法了。它从全局变量 `loadable_classes` 中取出所有可供调用的类,并进行清零操作。
630 |
631 | ```
632 | loadable_classes = nil;
633 | loadable_classes_allocated = 0;
634 | loadable_classes_used = 0;
635 | ```
636 |
637 | 其中 `loadable_classes` 指向用于保存类信息的内存的首地址,`loadable_classes_allocated` 标识已分配的内存空间大小,`loadable_classes_used` 则标识已使用的内存空间大小。
638 |
639 | 然后,循环调用所有类的 `+load` 方法。注意,这里是(调用分类的 `+load` 方法也是如此)直接使用函数内存地址的方式 `(*load_method)(cls, SEL_load)`; 对 `+load` 方法进行调用的,而不是使用发送消息 `objc_msgSend` 的方式。
640 |
641 | 但是如果我们写`[Student load]`时,这是使用发送消息 `objc_msgSend` 的方式。
642 |
643 | 举个🌰:
644 |
645 | ```
646 | @interface Person : NSObject
647 | @end
648 | @implementation Person
649 | + (void)load{
650 | NSLog(@"%s",__func__);
651 | }
652 | @end
653 | ```
654 |
655 | ```
656 | @interface Student : Person
657 | @end
658 | @implementation Student
659 | //+ (void)load{
660 | // NSLog(@"%s",__func__);
661 | //}
662 | @end
663 | ```
664 |
665 | ```
666 | int main(int argc, const char * argv[]) {
667 | @autoreleasepool {
668 | [Student load];
669 | }
670 | return 0;
671 | }
672 | ```
673 | 输出如下:
674 |
675 | 
676 |
677 | 第一句走的是`load`的加载方式,而第二句走的是`objc_msgSend`中消息发送机制,`isa`指针通过`superclass`在父类中找到类方法。
678 |
679 | *小总结*:
680 |
681 | * `+load`方法会在runtime加载类、分类时调用
682 | * 每个类、分类的+load,在程序运行过程中只调用一次
683 | * 调用顺序
684 |
685 | > 1.先调用类的+load
686 | > >按照编译先后顺序调用(先编译,先调用)
687 | > >调用子类的+load之前会先调用父类的+load
688 |
689 | > 2.再调用分类的+load
690 | >> 按照编译先后顺序调用(先编译,先调用)
691 |
692 |
693 |
694 | #### initialize源码分析
695 | 同样的,我们按照如下函数的调用顺序,一步一步的研究`initialize `的加载处理过程
696 |
697 | ```
698 | Method class_getInstanceMethod(Class cls, SEL sel);
699 | └── IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
700 | └── IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);
701 | └── void _class_initialize(Class cls);
702 | └── void callInitialize(Class cls);
703 |
704 | ```
705 | 我们直接打开`objc-runtime-new.mm`文件来研究`lookUpImpOrForward`这个方法
706 |
707 | ##### lookUpImpOrForward
708 |
709 | ```
710 | IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
711 | bool initialize, bool cache, bool resolver)
712 | {
713 | ...
714 | rwlock_unlock_write(&runtimeLock);
715 | }
716 |
717 | if (initialize && !cls->isInitialized()) {
718 | _class_initialize (_class_getNonMetaClass(cls, inst));
719 | // If sel == initialize, _class_initialize will send +initialize and
720 | // then the messenger will send +initialize again after this
721 | // procedure finishes. Of course, if this is not being called
722 | // from the messenger then it won't happen. 2778172
723 | }
724 |
725 | // The lock is held to make method-lookup + cache-fill atomic
726 | // with respect to method addition. Otherwise, a category could
727 | ...
728 | }
729 |
730 | ```
731 | `initialize && !cls->isInitialized()`判断代码表明当一个类需要初始化却没有初始化时,会调用`_class_initialize `进行初始化。
732 |
733 | ##### _class_initialize
734 | ```
735 | void _class_initialize(Class cls)
736 | {
737 | ...
738 | Class supercls;
739 | BOOL reallyInitialize = NO;
740 |
741 | // Make sure super is done initializing BEFORE beginning to initialize cls.
742 | // See note about deadlock above.
743 | supercls = cls->superclass;
744 | if (supercls && !supercls->isInitialized()) {
745 | _class_initialize(supercls);
746 | }
747 | ...
748 |
749 | callInitialize(cls);
750 |
751 | ...
752 | }
753 | ```
754 | 同样的`supercls && !supercls->isInitialized()`表明对入参的父类进行了递归调用,以确保父类优先于子类初始化。
755 |
756 | ##### callInitialize
757 | ```
758 | void callInitialize(Class cls)
759 | {
760 | ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
761 | asm("");
762 | }
763 | ```
764 | 最后在`callInitialize `中通过发送消息 `objc_msgSend` 的方式对 `+initialize `方法进行调用,也就是说`+ initialize `与一般普通方法的调用处理是一样的。
765 | 举个🌰:
766 |
767 | ```
768 | @interface Person : NSObject
769 | @end
770 | @implementation Person
771 | + (void)initialize{
772 | NSLog(@"%s",__func__);
773 | }
774 | @end
775 | @implementation Person (Eat)
776 | + (void)initialize{
777 | NSLog(@"%s",__func__);
778 | }
779 | @end
780 | ```
781 |
782 | ```
783 | @interface Student : Person
784 | @end
785 | @implementation Student
786 | + (void)initialize{
787 | NSLog(@"%s",__func__);
788 | }
789 | @end
790 | ```
791 | ```
792 | @interface Teacher : Person
793 | @end
794 | @implementation Teacher
795 | @end
796 | ```
797 |
798 | ```
799 | int main(int argc, const char * argv[]) {
800 | @autoreleasepool {
801 | [Student alloc];
802 | [Student initialize];
803 | [Person alloc];
804 | [Person alloc];
805 | [Person alloc];
806 | [Person alloc];
807 | [Person alloc];
808 | [Person alloc];
809 | NSLog(@"****分割线***");
810 | [Teacher alloc];
811 | [Teacher initialize];
812 | }
813 | return 0;
814 | }
815 | ```
816 | 输出如下:
817 | 
818 |
819 |
820 | *小总结*:
821 |
822 | * `+initialize`方法会在类第一次接收到消息时调用
823 | * 调用顺序
824 |
825 | > 1. 先调用父类的+initialize,再调用子类的+initialize
826 | > 2. 先初始化父类,再初始化子类,每个类只会初始化1次
827 |
828 | #### load与initialize对比
829 |
830 |
831 | |条件 | +load |+initialize|
832 | | --- | --- | --- |
833 | |关键方法 | `(*load_method)(cls, SEL_load)` | `objc_msgSend` |
834 | |调用时机 |被添加到 runtime 时 |收到第一条消息前,可能永远不调用|
835 | |调用顺序 |父类->子类->分类 |父类->子类|
836 | |调用次数 |1次 |多次|
837 | |是否需要显式调用父类实现 |否 |否|
838 | |是否沿用父类的实现 |否 |是|
839 | |分类中的实现 |类和分类都执行 |覆盖类中的方法,只执行分类的实现|
840 |
841 |
842 | #### 面试题
843 |
844 | ##### 1.Category的使用场合是什么?
845 |
846 | * 1. 给现有的类添加方法
847 | * 2. 将一个类的实现拆分成多个独立的源文件
848 | * 3. 声明私有的方法
849 |
850 | ##### 2.Category和Class Extension的区别是什么?
851 |
852 | * 1. Class Extension是编译时决议,在编译的时候,它的数据就已经包含在类信息中
853 | * 2. Category是运行时决议,在运行时,才会将数据合并到类信息中(可通过上面的动画进行理解`^_^`)
854 |
855 | ##### 3.Category的实现原理?
856 |
857 | * 1. Category编译之后的底层结构是`struct category_t`,里面存储着分类的对象方法、类方法、属性、协议信息
858 | * 2. 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)(依旧可通过上面的动画进行理解`-_-||`))
859 |
860 | ##### 4.一个类的有多个分类方法,分类中都含有与原类同名的方法,请问调用改方法时会调用谁的方法?分类会覆盖原类的方法吗?
861 | 不会覆盖!所有分类的方法会在运行时将它们的方法都合并到一个大数组中,后面参与编译的Category数据,会在数组的前面,然后再将该数组合并到类信息中,调用时顺着方法列表的顺序查找。
862 |
863 | ##### 5.load与initialize的区别
864 | 见`load`与`initialize`对比章节的表格
865 |
866 | ##### 6.Category能否添加成员变量?如果可以,如何给Category添加成员变量?
867 | 不能直接给Category添加成员变量,但是可以通过关联对象或者全局字典等方式间接实现Category有成员变量的效果
868 |
869 |
870 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iOSDeepAnalyse
2 |
3 | 
4 |
5 |
6 | ## 为什么要建这个仓库
7 | 听说文章写得多写得好能涨工资,因此打算试一下。。。
8 |
9 | ## 目录
10 |
11 | | Project | Article | Time|
12 | |---|---| ---|
13 | | Category| [Category:从底层原理研究到面试题分析](https://github.com/MisterBooo/iOSDeepAnalyse/blob/master/Article/Category:从底层原理研究到面试题分析.md)| 2018年09月19日|
--------------------------------------------------------------------------------