//// Note: only for use by objc runtime// Register handlers to be called when objc images are mapped, unmapped, and initialized.// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.// 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 will call the "mapped" function with already loaded objc images. During any later dlopen() call,// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called// 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);
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/voidmap_images(unsigned count,constchar*const paths[],conststruct mach_header *const mhdrs[]){
mutex_locker_t lock(runtimeLock);returnmap_images_nolock(count, paths, mhdrs);}
for(EACH_HEADER){if(!mustReadClasses(hi, hasDyldRoots)){// Image is sufficiently optimized that we need not call readClass()continue;}
classref_t const*classlist =_getObjc2ClassList(hi,&count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();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;}}}
if(cls->isStubClass()){// Stub classes are never realized. Stub classes// don't know their metaclass until they're// initialized, so we have to add categories with// class methods or properties to the stub itself.// methodizeClass() will find them and add them to// the metaclass as appropriate.if(cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||(hasClassProperties && cat->_classProperties)){// 如果分类中有实例方法、协议、实例属性,就会改写 target class 的结构
objc::unattachedCategories.addForClass(lc, cls);}}else{// First, register the category with its target class.// Then, rebuild the class's method lists (etc) if// the class is realized.if(cat->instanceMethods || cat->protocols
|| cat->instanceProperties){// 如果分类中有类方法、协议或类属性,会改写 target class 的元类结构if(cls->isRealized()){attachCategories(cls,&lc,1, ATTACH_EXISTING);}else{
objc::unattachedCategories.addForClass(lc, cls);}}if(cat->classMethods || cat->protocols
||(hasClassProperties && cat->_classProperties)){if(cls->ISA()->isRealized()){attachCategories(cls->ISA(),&lc,1, ATTACH_EXISTING | ATTACH_METACLASS);}else{
objc::unattachedCategories.addForClass(lc, cls->ISA());}}}
不管是对 cls 还是 cls 的元类进行操作,都是调用的方法 addUnattachedCategoryForClass,但这个方法并不是 category 实现的关键,其内部逻辑只是将 class 和其对应的 category 做一个映射,这样以 class 为 key 就可以取到所其对应的所有的 category;
做好 class 和 category 的映射后,会调用 remethodizeClass 方法来修改 class 的 method list 结构,这才是 runtime 实现 category 的关键所在。
remethodizeClass:
staticvoidremethodizeClass(Class cls){
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();// 通过 unattachedCategoriesForClass 取出还未被附加到 class 上的 category listif((cats =unattachedCategoriesForClass(cls, false))){if(PrintConnecting){_objc_inform("CLASS: attaching categories to class '%s' %s", cls->nameForLogging(), isMeta ?"(meta)":"")}// attachCategories 将这些 category 附加在 class 上attachCategories(cls, cats, true);free(cats);}}
// Attach method lists and properties and protocols from categories to a class.// Assumes the categories in cats are all loaded and sorted by load order, // oldest categories first.staticvoidattachCategories(Class cls,const locstamped_category_t *cats_list, uint32_t cats_count,int flags){if(slowpath(PrintReplacedMethods)){printReplacements(cls, cats_list, cats_count);}if(slowpath(PrintConnecting)){_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count,(flags & ATTACH_EXISTING)?" existing":"",
cls->nameForLogging(),(flags & ATTACH_METACLASS)?" (meta)":"");}/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/// 分别以 cats->counts 的大小为例,创建方法列表、属性列表、协议列表
constexpr uint32_t ATTACH_BUFSIZ =64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];// ...
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);}