012:类的加载上:mapImage-[readClass->UnfixedSelectors->readProtocol]->realizeClassWithoutSwift->[methodizeClass->prepareMethodLists->fixupMethodList]->attachCategories)、loadImage(加载load方法),
问题
readImage->(readClass->UnfixedSelectors->readProtocol)->realizeClassWithoutSwift->methodizeClass->attachCategories)
1: readImage
2:readClass->UnfixedSelectors->readProtocol
3:realizeClassWithoutSwift:(ro和rw数据、继承和isa关系)realize:实现
4:methodizeClass,排序,优化,将ro数据放入rw
5:attachCategories
6:loadImage
目录
预备
正文
一:map_images和load_images
我们理解了dyld
与objc
是如何关联的,本文的主要目的是理解类的相关信息
是如何加载
到内存
的,其中重点关注map_images
和load_images
-
map_images
:主要是管理文件中和动态库中
的所有符号
,即class、protocol、selector、category
等 -
load_images
:加载执行load方法
其中代码
通过编译
,读取到Mach-O可执行文件
中,再从Mach-O中读取到内存
,如下图所示
二:map_images源码流程
1:map_images
方法的主要作用是将Mach-O
中的类信息
加载到内存
管理文件和动态库中所有的符号,如class
、Protocol
、selector
、category
等
从系统库 libSystem
的 Runtime
入口函数 _objc_init
跳转到 map_images
/*********************************************************************** * 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 **********************************************************************/ 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); }
2:map_images_nolock 分析
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { //...省略 // Find all images with Objective-C metadata.查找所有带有Objective-C元数据的映像 hCount = 0; // Count classes. Size various table based on the total.计算类的个数 int totalClasses = 0; int unoptimizedTotalClasses = 0; //代码块:作用域,进行局部处理,即局部处理一些事件 { //...省略 } //...省略 if (hCount > 0) { //加载镜像文件 _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } firstTime = NO; // Call image load funcs after everything is set up.一切设置完成后,调用镜像加载功能。 for (auto func : loadImageFuncs) { for (uint32_t i = 0; i < mhCount; i++) { func(mhdrs[i]); } } }
_read_images
主要是主要是加载类信息
,即类、分类、协议等,进入_read_images
源码实现,主要分为以下几部分:
- 1、条件控制进行的一次加载
- 2、修复预编译阶段的@selector的混乱问题
- 3、错误混乱的类处理
- 4、修复重映射一些没有被镜像文件加载进来的类
- 5、修复一些消息
- 6、当类里面有协议时:readProtocol 读取协议
- 7、修复没有被加载的协议
- 8、分类处理
- 9、类的加载处理
- 10、没有被处理的类,优化那些被侵犯的类
1、条件控制进行的一次加载
在doneOnce
流程中通过NXCreateMapTable
创建表,存放类信息,即创建一张类的哈希表``gdb_objc_realized_classes
,其目的是为了类查找方便、快捷
if (!doneOnce) { //...省略 // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; //创建表(哈希表key-value),目的是查找快 gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); ts.log("IMAGE TIMES: first time tasks"); }
- 这里拓展一下这张表的类型:
gdb_objc_realized_classes
的类型是NXMapTable
。 - 可以简单理解
NXMapTable
==NSMapTable
,也就是对应的我们常用的NSDictionary
,并且额外提供了 weak 指针来使用垃圾回收机制。 NSDictionary
底层实现也是使用了NSMapTable
(散列表),(备注:苹果官网并没有这些类的实现,想要查看NSDictionary
和NSArray
的实现源码可以去 GNUstep官网下载。- 使用
NSMapTable
是因为它更强大 NSMapTable 相对于 NSDictionary 的优势。
2. 修复预编译阶段的 @selector
的混乱问题
// Fix up @selector references 修复@selector引用 //sel 不是简单的字符串,而是带地址的字符串 static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->hasPreoptimizedSelectors()) continue; bool isBundle = hi->isBundle(); //通过_getObjc2SelectorRefs拿到Mach-O中的静态段__objc_selrefs SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count; for (i = 0; i < count; i++) { //列表遍历 const char *name = sel_cname(sels[i]); //注册sel操作,即将sel添加到 SEL sel = sel_registerNameNoLock(name, isBundle); if (sels[i] != sel) {//当sel与sels[i]地址不一致时,需要调整为一致的 sels[i] = sel; } } } }
_getObjc2SelectorRefs
拿到Mach_O中的静态段__objc_selrefs
,遍历列表调用sel_registerNameNoLock
将SEL
添加到namedSelectors
哈希表中_getObjc2SelectorRefs
的源码如下,表示获取Mach-O中的静态段__objc_selrefs
,后续通过_getObjc2
开头的Mach-O静态段获取,都对应不同的section name// 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 const, "__objc_classlist"); GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist"); GETSECT(_getObjc2CategoryList, category_t * const, "__objc_catlist"); GETSECT(_getObjc2CategoryList2, category_t * const, "__objc_catlist2"); GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist"); GETSECT(_getObjc2ProtocolList, protocol_t * const, "__objc_protolist"); GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs"); GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
sel_registerNameNoLock
源码路径如下:sel_registerNameNoLock -> __sel_registerName
,如下所示,其关键代码是auto it = namedSelectors.get().insert(name);
,即将sel插入namedSelectors
哈希表SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy } static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; if (shouldLock) selLock.assertUnlocked(); else selLock.assertLocked(); if (!name) return (SEL)0; result = search_builtins(name); if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); auto it = namedSelectors.get().insert(name);//sel插入表 if (it.second) { // No match. Insert. *it.first = (const char *)sel_alloc(name, copy); } return (SEL)*it.first; }
其中selector --> sel
并不是简单的字符串,是带地址的字符串
如下所示,sels[i]
与sel
字符串一致,但是地址不一致,所以需要调整为一致的
3: 错误混乱的类处理
主要是从Mach-O中取出所有类,在遍历进行处理
//3、错误混乱的类处理 // Discover classes. Fix up unresolved future classes. Mark bundle classes. bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); //读取类:readClass for (EACH_HEADER) { if (! mustReadClasses(hi, hasDyldRoots)) { // Image is sufficiently optimized that we need not call readClass() continue; } //从编译后的类列表中取出所有类,即从Mach-O中获取静态段__objc_classlist,是一个classref_t类型的指针 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];//此时获取的cls只是一个地址 Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); //读取类,经过这步后,cls获取的值才是一个名字 //经过调试,并未执行if里面的流程 //初始化所有懒加载的类需要的内存空间,但是懒加载类的数据现在是没有加载到的,连类都没有初始化 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");
通过代码调试,知道了在未执行readClass
方法前,cls只是一个地址
执行readClass后,cls获取的值才是一个名字
可以看到在 readClass
方法调用之后,对 cls
进行了类名的赋值操作。此时类的信息目前仅存储了地址和名称,我们再进去看一下 readClass
的源码
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { const char *mangledName = cls->mangledName(); if (headerIsPreoptimized && !replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls == getClass(name)); ASSERT(getClassExceptSomeSwift(mangledName)); } else { // 添加类名 addNamedClass(cls, mangledName, replacing); // 插入哈希表中,即从 mach-o 中把类读取到内存当中 addClassTableEntry(cls); } return cls; }
再进去查看 addNamedClass
的源码
static void addNamedClass(Class cls, const char *name, Class replacing = nil) { Class old; if ((old = getClassExceptSomeSwift(name)) && old != replacing) { inform_duplicate(name, old, cls); // getMaybeUnrealizedNonMetaClass uses name lookups. // Classes not found by name lookup must be in the // secondary meta->nonmeta table. addNonMetaClass(cls); } else { // 将 name 与 cls 的地址进行映射,并插入到内存当中 NXMapInsert(gdb_objc_realized_classes, name, cls); } }
查看 mangledName
方法的源码
const char *mangledName() { // fixme can't assert locks here ASSERT(this); // 这个初始化判断在我们前面分析的 lookuoImp 也有出现过 if (isRealized() || isFuture()) { // 如果类已经初始化过,则从 ro 中直接获取 name return data()->ro()->name; } else { // 否则从 mach-o 中读取 data 里面的 name return ((const class_ro_t *)data())->name; } }
综上可得:readClass
的主要作用就是将 mach-o
的类读取到内存当中,当前的类中仅有两个信息,即地址和名称,data
数据会在步骤九中读取出来并赋值到类中
4: 修复重映射一些没有被镜像文件加载进来的类
//4、修复重映射一些没有被镜像文件加载进来的类 // Fix up remapped classes 修正重新映射的类 // Class list and nonlazy class list remain unremapped.类列表和非惰性类列表保持未映射 // Class refs and super refs are remapped for message dispatching.类引用和超级引用将重新映射以进行消息分发 //经过调试,并未执行if里面的流程 //将未映射的Class 和 Super Class重映射,被remap的类都是懒加载的类 if (!noClassesRemapped()) { for (EACH_HEADER) { Class *classrefs = _getObjc2ClassRefs(hi, &count);//Mach-O的静态段 __objc_classrefs for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); } // fixme why doesn't test future1 catch the absence of this? classrefs = _getObjc2SuperRefs(hi, &count);//Mach_O中的静态段 __objc_superrefs for (i = 0; i < count; i++) { remapClassRef(&classrefs[i]); } } } ts.log("IMAGE TIMES: remap classes");
主要是将未映射的Class
和Super Class
进行重映射,其中
-
_getObjc2ClassRefs
是获取Mach-O
中的静态段__objc_classrefs
即类的引用
-
_getObjc2SuperRefs
是获取Mach-O
中的静态段__objc_superrefs
即父类的引用
-
通过注释可以得知,被
remapClassRef
的类都是懒加载的类
,所以最初经过调试时,这部分代码是没有执行的
5: 修复一些消息
#if SUPPORT_FIXUP //5、修复一些消息 // Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) { // _getObjc2MessageRefs 获取Mach-O的静态段 __objc_msgrefs 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里面的流程 //遍历将函数指针进行注册,并fix为新的函数指针 for (i = 0; i < count; i++) { fixupMessageRef(refs+i); } } ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endif
6:当类里面有协议时:readProtocol 读取协议
//6、当类里面有协议时:readProtocol 读取协议 // Discover protocols. Fix up protocol refs. 发现协议。修正协议参考 //遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中 for (EACH_HEADER) { extern objc_class OBJC_CLASS_$_Protocol; //cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类 Class cls = (Class)&OBJC_CLASS_$_Protocol; ASSERT(cls); //获取protocol哈希表 -- protocol_map NXMapTable *protocol_map = protocols(); bool isPreoptimized = hi->hasPreoptimizedProtocols(); // Skip reading protocols if this is an image from the shared cache // and we support roots // Note, after launch we do need to walk the protocol as the protocol // in the shared cache is marked with isCanonical() and that may not // be true if some non-shared cache binary was chosen as the canonical // definition if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) { if (PrintProtocols) { _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s", hi->fname()); } continue; } bool isBundle = hi->isBundle(); //通过_getObjc2ProtocolList 获取到Mach-O中的静态段__objc_protolist协议列表, //即从编译器中读取并初始化protocol protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count); for (i = 0; i < count; i++) { //通过添加protocol到protocol_map哈希表中 readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); } } ts.log("IMAGE TIMES: discover protocols");
通过NXMapTable *protocol_map = protocols();
创建protocol哈希表,表的名称为protocol_map
/*********************************************************************** * protocols * Returns the protocol name => protocol map for protocols. * Locking: runtimeLock must read- or write-locked by the caller **********************************************************************/ static NXMapTable *protocols(void) { static NXMapTable *protocol_map = nil; runtimeLock.assertLocked(); INIT_ONCE_PTR(protocol_map, NXCreateMapTable(NXStrValueMapPrototype, 16), NXFreeMapTable(v) ); return protocol_map; }
通过_getObjc2ProtocolList
获取到Mach-O中的静态段__objc_protolist
协议列表,即从编译器中读取并初始化protocol
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
循环遍历协议列表,通过readProtocol
方法将协议添加到protocol_map哈希表中
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
7、修复没有被加载的协议
_getObjc2ProtocolRefs
获取到Mach-O的静态段 __objc_protorefs
(与6中的__objc_protolist
并不是同一个东西),然后遍历需要修复的协议,通过remapProtocolRef
比较当前协议和协议列表中的同一个内存地址的协议是否相同
,如果不同则替换
//7、修复没有被加载的协议 // Fix up @protocol references // Preoptimized images may have the right // answer already but we don't know for sure. for (EACH_HEADER) { // At launch time, we know preoptimized image refs are pointing at the // shared cache definition of a protocol. We can skip the check on // launch, but have to visit @protocol refs for shared cache images // loaded later. if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized()) continue; //_getObjc2ProtocolRefs 获取到Mach-O的静态段 __objc_protorefs protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) {//遍历 //比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换 remapProtocolRef(&protolist[i]);//经过代码调试,并未执行 } } ts.log("IMAGE TIMES: fix up @protocol references");
其中remapProtocolRef
的源码实现如下
/*********************************************************************** * remapProtocolRef * Fix up a protocol ref, in case the protocol referenced has been reallocated. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static size_t UnfixedProtocolReferences; static void remapProtocolRef(protocol_t **protoref) { runtimeLock.assertLocked(); //获取协议列表中统一内存地址的协议 protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref); if (*protoref != newproto) {//如果当前协议 与 同一内存地址协议不同,则替换 *protoref = newproto; UnfixedProtocolReferences++; } }
8、分类处理
主要是处理分类
,需要在分类初始化并将数据加载到类后才执行,对于运行时出现的分类,将分类的发现推迟推迟到对_dyld_objc_notify_register
的调用完成后的第一个load_images
调用为止
//8、分类处理 // Discover categories. Only do this after the initial category 发现分类 // attachment has been done. For categories present at startup, // discovery is deferred until the first load_images call after // the call to _dyld_objc_notify_register completes. rdar://problem/53119145 if (didInitialAttachCategories) { for (EACH_HEADER) { load_categories_nolock(hi); } } ts.log("IMAGE TIMES: discover categories");
9、类的加载处理
// Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { classref_t const *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; addClassTableEntry(cls); if (cls->isSwiftStable()) { if (cls->swiftMetadataInitializer()) { _objc_fatal("Swift class %s with a metadata initializer " "is not allowed to be non-lazy", cls->nameForLogging()); } // fixme also disallow relocatable classes // We can't disallow all Swift classes because of // classes like Swift.__EmptyArrayStorage } realizeClassWithoutSwift(cls, nil); } }
通过注释,我们可以得知,当前只有 类为非懒加载类的时候,才会执行。
1. 从 mach-o
的静态段 __objc_nlclslist
中获取非懒加载类表。
2. 通过 addClassTableEntry
将非懒加载的类插入到列表中,然后存储到内存当中,其中如果当前类已经被添加,就不会再次进行添加了。
3. 通过 realizeClassWithoutSwift
实现类的 data
的加载,因为前面我们的类的信息目前仅存储了地址和名称。
realizeClassWithoutSwift
方法realizeClassWithoutSwift
方法主要作用是将类的 data
信息加载到内存当中,还有对 ro
和 rw
进行了相关操作。主要有以下几个步骤。
1. 读取 data
数据,并设置 ro
、rw
。
- 读取
data
数据,并将其强转为ro
,以及初始化rw
,然后将ro
拷贝一份到rw
的ro
。 ro
表示read only
,其在编译期的时候就已经确定了内存,包含了类的名称、方法列表、协议列表、属性列表和成员变量列表的信息。由于它是只读的,确定了之后就不会发生变化,所以属于 干净的内存(Clean Memory)。rw
表示read Write
,由于OC
的动态性,所以可能会在运行时动态往类中添加属性、方法和协议。rwe
表示read Write ext
,在2020的WWDC上,对 内存优化做了进一步的改进。其中说到rw
中只有 10% 左右的类真正更改了它们的方法、属性等,所以新增加了rwe
,即是类的额外信息,rw
和rwe
都属于 脏内存(dirty memory)。
// fixme verify class is not in an un-dlopened part of the shared cache? // 读取 cls 的 data,并将其强转为 ro,以及初始化 rw,然后将 ro 拷贝一份到 rw 的 ro。 // 读取类结构的 bits 属性。 auto ro = (const class_ro_t *)cls->data(); // 判断是否是元类 auto isMeta = ro->flags & RO_META; if (ro->flags & RO_FUTURE) { // This was a future class. rw data is already allocated. rw = cls->data(); ro = cls->data()->ro(); ASSERT(!isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // Normal class. Allocate writeable class data. // 创建 rw rw = objc::zalloc<class_rw_t>(); // rw 赋值 ro rw->set_ro(ro); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; // 将 rw 赋值给 cls 的 data cls->setData(rw); }
2:递归调用 realizeClassWithoutSwift
完成类的继承链关系。
- 递归调用
realizeClassWithoutSwift
,设置父类和元类的data
信息加载到内存当中。 - 分别将父类和元类赋值给
class
的superclass
和classIsa
。
// Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. // This assumes that none of those classes have Swift contents, // or that Swift's initializers have already been called. // fixme that assumption will be wrong if we add support // for ObjC subclasses of Swift classes. // 递归调用 `realizeClassWithoutSwift`,设置父类和元类的 `data` 信息加载到内存当中。 // 当 isa 找到根元类之后,因为根元类的 isa 是指向自己的,不会返回 nil 从而导致无限递归,在 remapClass 中对类在表中进行查找的操作,如果表中已有该类,则返回一个空值;如果没有则返回当前类,这样保证了类只加载一次并结束递归 supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); // Update superclass and metaclass in case of remapping // 分别将父类和元类赋值给 `class` 的 `superclass` 和 `classIsa`。 cls->superclass = supercls; cls->initClassIsa(metacls); // Connect this class to its superclass's subclass lists // 注意:class 是双向链接结构,即父类可以找到子类,子类可以找到父类 if (supercls) { // 将 cls 作为 supercls 的子类添加。 addSubclass(supercls, cls); } else { // 将 cls 添加为新的已实现的根类。 addRootClass(cls); }
3:调用 methodizeClass
方法,读取方法列表(包括分类)、协议列表、属性列表,然后赋值给 rw
,最后返回 cls
。
// Attach categories - 附加分类 methodizeClass(cls, previously); return cls;
10: 没有被处理的类,优化那些被侵犯的类
// Realize newly-resolved future classes, in case CF manipulates them // 10.实现没有被处理的类,优化那些被侵犯的类 if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { Class cls = resolvedFutureClasses[i]; if (cls->isSwiftStable()) { _objc_fatal("Swift class is not allowed to be future"); } realizeClassWithoutSwift(cls, nil); cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); }
3: methodizeClass
分析对rw进行赋值
methodizeClass->prepareMethodLists->
fixupMethodList
3. 1:methodizeClass
/*********************************************************************** * methodizeClass * Fixes up cls's method list, protocol list, and property list. * Attaches any outstanding categories. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void methodizeClass(Class cls, Class previously) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro(); auto rwe = rw->ext(); // Install methods and properties that the class implements itself. // 将方法列表、属性列表和协议列表赋值到 rwe // 获取 ro 的 方法列表 method_list_t *list = ro->baseMethods(); if (list) { // 对方法列表进行排序操作 prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); // 把方法赋值给 rwe 的 method if (rwe) rwe->methods.attachLists(&list, 1); } // 获取 ro 的 属性列表 property_list_t *proplist = ro->baseProperties; if (rwe && proplist) { // 把属性赋值给 rwe 的 properties rwe->properties.attachLists(&proplist, 1); } // 获取 ro 的 协议列表 protocol_list_t *protolist = ro->baseProtocols; if (rwe && protolist) { // 把协议赋值给 rwe 的 protocols rwe->protocols.attachLists(&protolist, 1); } // Root classes get bonus method implementations if they don't have // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO); } // Attach categories. // 附加分类方法 if (previously) { if (isMeta) { objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS); } else { // When a class relocates, categories with class methods // may be registered on the class itself rather than on // the metaclass. Tell attachToClass to look for those. objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS); } } objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS); }
以上代码,可以分为
- 将方法列表、属性列表和协议列表赋值给
rwe
。 - 附加分类方法。
3.1 rwe 的获取
class_rw_ext_t *ext() const { return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(); }
在上面的源码中我们可以看到 ext()
方法调用了 get_ro_or_rwe()
获取 ro
或者 rwe()
。
3.2 rwe 的逻辑
以 方法列表加入到 rwe
的逻辑为例,主要有以下步骤
1. 获取 ro
的 baseMethods
;
2. 通过 prepareMethodLists
方法进行排序;
3. 通过 attachLists
插入。
3.2.1 方法如何排序
查看 prepareMethodLists
的源码,发现内部是通过调用 fixupMethodList
来进行排序的
static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle) { for (int i = 0; i < addedCount; i++) { method_list_t *mlist = addedLists[i]; ASSERT(mlist); // Fixup selectors if necessary if (!mlist->isFixedUp()) { // 重点部分 fixupMethodList(mlist, methodsFromBundle, true/*sort*/); } } }
查看 fixupMethodList
的源码,发现内部是根据 SortBySELAddress
,即根据 sel
的地址进行排序的。
static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); ASSERT(!mlist->isFixedUp()); // fixme lock less in attachMethodLists ? // dyld3 may have already uniqued, but not sorted, the list if (!mlist->isUniqued()) { mutex_locker_t lock(selLock); // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name); meth.name = sel_registerNameNoLock(name, bundleCopy); } } // Sort by selector address. // 根据 `sel` 的地址进行排序 if (sort) { method_t::SortBySELAddress sorter; std::stable_sort(mlist->begin(), mlist->end(), sorter); } // Mark method list as uniqued and sorted mlist->setFixedUp(); }
4:主要是将分类添加到主类中
void attachToClass(Class cls, Class previously, int flags) { runtimeLock.assertLocked(); ASSERT((flags & ATTACH_CLASS) || (flags & ATTACH_METACLASS) || (flags & ATTACH_CLASS_AND_METACLASS)); auto &map = get(); // 找到一个分类就进来一次,防止混乱 auto it = map.find(previously); // 注意:当主类没有实现 load 方法的时候,分类实现了 load 方法,那么会迫使主类进行加载,所以会进入 if 判断里面 if (it != map.end()) { category_list &list = it->second; // 判断是否元类 if (flags & ATTACH_CLASS_AND_METACLASS) { int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS; // 附加实例方法 attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS); // 附加类方法 attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS); } else { // 附加实例方法 attachCategories(cls, list.array(), list.count(), flags); } map.erase(it); } }
2.3.4 attachCategories 方法
主要是准备分类的数据
// 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. static void attachCategories(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. */ constexpr uint32_t ATTACH_BUFSIZ = 64; method_list_t *mlists[ATTACH_BUFSIZ]; property_list_t *proplists[ATTACH_BUFSIZ]; protocol_list_t *protolists[ATTACH_BUFSIZ]; uint32_t mcount = 0; uint32_t propcount = 0; uint32_t protocount = 0; bool fromBundle = NO; bool isMeta = (flags & ATTACH_METACLASS); // 初始化 rwe,因为要往主类添加方法、属性和协议 auto rwe = cls->data()->extAllocIfNeeded(); // mlists -> 二维数组 for (uint32_t i = 0; i < cats_count; i++) { auto& entry = cats_list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { // 当前 mcount = 0,ATTACH_BUFSIZ= 64,所以不会进入 if 里面 if (mcount == ATTACH_BUFSIZ) { prepareMethodLists(cls, mlists, mcount, NO, fromBundle); rwe->methods.attachLists(mlists, mcount); mcount = 0; } // 倒序插入 mlists[ATTACH_BUFSIZ - ++mcount] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { if (propcount == ATTACH_BUFSIZ) { rwe->properties.attachLists(proplists, propcount); propcount = 0; } proplists[ATTACH_BUFSIZ - ++propcount] = proplist; } protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta); if (protolist) { if (protocount == ATTACH_BUFSIZ) { rwe->protocols.attachLists(protolists, protocount); protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; } } if (mcount > 0) { // 排序 prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle); // 进行内存平移操作 rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); if (flags & ATTACH_EXISTING) flushCaches(cls); } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); }
2.3.5 attachLists 方法
void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists // 获取数组中旧 lists 的大小 uint32_t oldCount = array()->count; // 计算新的容量大小 = 旧数据大小 + 新数据大小 uint32_t newCount = oldCount + addedCount; // 根据新的容量大小,开辟一个数组,类型是 array_t,通过 array() 获取 setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 设置数组的大小 array()->count = newCount; // 旧的数据从 addedCount 数组下标开始存放旧的 lists,大小 = 旧数据大小 * 单个旧 list 大小 memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); // 新数据从数组 首位置开始存储,存放新的lists,大小 = 新数据大小 * 单个list大小 memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (!list && addedCount == 1) { // 0 lists -> 1 list // 将 list 加入 mlists 的第一个元素,此时的 list 是一个一维数组 list = addedLists[0]; } else { // 新的 list 就是分类,来自LRU的算法思维,即最近最少使用算法 // 获取旧的list List* oldList = list; uint32_t oldCount = oldList ? 1 : 0; // 计算容量和 = 旧list个数 + 新lists的个数 uint32_t newCount = oldCount + addedCount; // 开辟一个容量和大小的集合,类型是 array_t,即创建一个数组,放到 array 中,通过 array() 获取 setArray((array_t *)malloc(array_t::byteSize(newCount))); // 设置数组的大小 array()->count = newCount; // 判断old是否存在,如果存在,就将旧的 list 放入到数组的末尾 if (oldList) array()->lists[addedCount] = oldList; // memcpy(起始位置,放什么,放多大) 内存平移,从数组起始位置存入新的list //其中 array()->lists 表示首位元素位置 memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } }
从上可知,插入表可分为以下三种情况
- 1. 多对多: 如果当前调用
attachLists
的list_array_tt
二维数组中有多个一维数组。
- 1.1 先计算数组中旧
list
的大小 - 1.2 计算新的容量大小 = 旧数据大小 + 新数据大小
- 1.3 根据新的容量大小,开辟一个数组,类型是
array_t
,通过array()
获取 - 1.4 设置数组大小
- 1.5 旧的数据从
addedCount
数组下标开始 存放旧的lists
,大小 = 旧数据大小 * 单个旧list大小,即指针偏移 - 1.6 新数据从数组首位置开始存储,存放新的
lists
,大小 = 新数据大小 * 单个list大小,可以理解越晚加进来的越在前面,越在前面,调用时则优先调用
- 2. 零对一: 如果当前调用
attachLists
的list_array_tt
二维数组为空且新增大小为1。
- 2.1 直接赋值给
addedList
的第一个list
- 3. 一对多: 如果当前调用
attachLists
的list_array_tt
二维数组只有一个一维数组。
- 3.1 获取旧的list
- 3.2 计算容量和 = 旧list个数 + 新lists的个数
- 3.3 开辟一个容量和大小的集合,类型是
array_t
,即创建一个数组,放到array
中,通过array()
获取 - 3.4 设置数组的大小
- 3.5 判断
old
是否存在,如果old
是存在的,那么就将旧的list放入到数组的末尾 - 3.6 memcpy(起始位置,放什么,放多大) 内存平移,从数组起始位置存入新的list
在第三种情况中,list
指的就是我们的分类,所以这就是为什么我们经常碰到的 子类实现父类方法会把父类方法覆盖、分类实现主类方法会把主类方法覆盖的原因了。
总结
readClass
主要是读取类,即此时的类仅有地址+名称,还没有data数据realizeClassWithoutSwift
主要是实现类,即将类的data数据读取到内存中-
methodizeClass
方法中实现类中方法
(协议等)的`序列化 -
attachCategories
方法中实现类以及分类的数据加载
-
综上所述,类从Mach-O加载到内存
的流程图如下所示
methodizeClass:
5:懒加载类和非懒加载类的数据加载时机
注意