├── Article └── Category:从底层原理研究到面试题分析.md └── README.md /Article/Category:从底层原理研究到面试题分析.md: -------------------------------------------------------------------------------- 1 | #
Category:从底层原理研究到面试题分析
2 | 3 | ![](http://oriq21dog.bkt.clouddn.com/20180916144028.png) 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 | ![](http://oriq21dog.bkt.clouddn.com/20180914095154.gif) 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 | ![](http://oriq21dog.bkt.clouddn.com/20180914171151.png) 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 | ![](http://oriq21dog.bkt.clouddn.com/20180919091309.png) 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 | ![](http://oriq21dog.bkt.clouddn.com/20180919153011.png) 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 | ![](http://oriq21dog.bkt.clouddn.com/20180827090107.jpg) 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日| --------------------------------------------------------------------------------