1、往期文章

iOS 底层探索文章系列

上一篇中,我们已经分析过了 dyld 与我们的 objc 之间是怎样关联起来的,结合上一篇文章中开头的面试题,那么今天我们继续探索 Runtimemap_images 做了哪些操作。

2、map_images 流程分析

还是从系统库 libSystemRuntime 入口函数 _objc_init 跳转到 map_images

  1. /***********************************************************************
  2. * map_images
  3. * Process the given images which are being mapped in by dyld.
  4. * Calls ABI-agnostic code after taking ABI-specific locks.
  5. *
  6. * Locking: write-locks runtimeLock
  7. **********************************************************************/
  8. void
  9. map_images(unsigned count, const char * const paths[],
  10. const struct mach_header * const mhdrs[])
  11. {
  12. mutex_locker_t lock(runtimeLock);
  13. return map_images_nolock(count, paths, mhdrs);
  14. }

2.1 map_images_nolock 分析

进来之后,我们发现直接返回了 map_images_nolock

map_images 函数的注释部分,可以得出: map_images 主要作用就是处理由 dyld 映射的 image(泛指二进制可执行程序)。

继续进入 map_images_nolock 函数的实现部分,代码比较长,我们主要关注类的信息是如何加载的

image.png

map_images_nolock 流程总结:

  • 1. 判断 firstTime,如果为 YES 那么准备执行环境初始化。
  • 2. 计算 class 数量,然后根据总数调整各种表的大小。
  • 3. 判断 firstTime,如果为 YES 则执行各种表的初始化操作。
  • 4. 执行 _read_images,对镜像文件进行读取,然后把 firstTime 置为 NO,下次进入直接执行 _read_images

2.2 _read_images 分析

接下来就是我们的重点部分,分析 _read_images 究竟做了哪些操作

image.png
image.png
image.png

_read_images 流程总结:

  • 1. 条件控制进行一次的加载。
  • 2. 修复预编译阶段的 @selector 的混乱问题。
  • 3. 错误混乱的类处理。
  • 4. 修复重映射一些没有被镜像文件加载进来的类。
  • 5. 修复一些消息。
  • 6. 当我们类里面有协议的时候: readProtocol
  • 7. 修复没有被加载的协议。
  • 8. 分类处理。
  • 9. 类的加载处理。
  • 10. 没有被处理的类,优化那些被侵犯的类。

下面我们对部分步骤的代码进行解读

2.2.1 步骤1. 条件控制进行一次的加载

  1. if (!doneOnce) {
  2. // 这个逻辑只执行一次
  3. doneOnce = YES;
  4. launchTime = YES;
  5. // 重置及初始化TaggedPointer环境
  6. if (DisableTaggedPointers) {
  7. disableTaggedPointers();
  8. }
  9. initializeTaggedPointerObfuscator();
  10. // 创建哈希表 gdb_objc_realized_classes
  11. int namedClassesSize =
  12. (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
  13. gdb_objc_realized_classes =
  14. NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
  15. }

我们到这个表的定义部分,根据注释能查看出这个表大概的作用。

  1. // This is a misnomer: gdb_objc_realized_classes is actually a list of
  2. // named classes not in the dyld shared cache, whether realized or not.
  3. // gdb_objc_realized_classes 实际上是用于存储不在共享缓存且已命名的类,无论它是否已实现
  4. NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
  • 这里拓展一下这张表的类型:gdb_objc_realized_classes 的类型是NXMapTable
  • 可以简单理解 NXMapTable == NSMapTable ,也就是对应的我们常用的 NSDictionary,并且额外提供了 weak 指针来使用垃圾回收机制
  • NSDictionary 底层实现也是使用了 NSMapTable(散列表),(备注:苹果官网并没有这些类的实现,想要查看 NSDictionaryNSArray 的实现源码可以去 GNUstep官网下载
  • 使用 NSMapTable 是因为它更强大 NSMapTable 相对于 NSDictionary 的优势

2.2.2 步骤2. 修复预编译阶段的 @selector 的混乱问题

  1. static size_t UnfixedSelectors;
  2. {
  3. mutex_locker_t lock(selLock);
  4. for (EACH_HEADER) {
  5. if (hi->hasPreoptimizedSelectors()) continue;
  6. bool isBundle = hi->isBundle();
  7. // _getObjc2SelectorRefs 是从 mach-o 中的静态段 __objc_selrefs 中遍历列表,然后通过 sel_registerNameNoLock 将 sel 添加到 namedSelectors 中
  8. SEL *sels = _getObjc2SelectorRefs(hi, &count);
  9. UnfixedSelectors += count;
  10. for (i = 0; i < count; i++) {
  11. const char *name = sel_cname(sels[i]);
  12. // 此处需要注意:sel 并不是一个简单的字符串,而是带地址的字符串
  13. SEL sel = sel_registerNameNoLock(name, isBundle);
  14. if (sels[i] != sel) {
  15. sels[i] = sel;
  16. }
  17. }
  18. }
  19. }

image.png

image.png

通过控制台打印输出的结果,我们可以看到两个方法的名称相同,但是方法的地址却不相同,主要原因是什么呢?

是因为,我们整个苹果系统中,会有很多系统框架,比如 CoreFoundationCoreMedia 等等,当每个框架都有一个相同的方法,比如上图的 class 方法的时候,我们就需要将方法平移到程序的最前面进行执行,例如 CoreFoundationclass 方法的 index = 0,而 CoreMediaclass 方法 index = 0 + CoreFoundation 的大小。所以我们要将方法进行平移操作。

2.2.3 步骤3: 错误混乱的类处理

  1. bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
  2. for (EACH_HEADER) {
  3. if (! mustReadClasses(hi, hasDyldRoots)) {
  4. // Image is sufficiently optimized that we need not call readClass()
  5. continue;
  6. }
  7. // 从 mach-o 的静态段 __objc_classlist 类列表中读取出所有类
  8. classref_t const *classlist = _getObjc2ClassList(hi, &count);
  9. bool headerIsBundle = hi->isBundle();
  10. bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
  11. for (i = 0; i < count; i++) {
  12. Class cls = (Class)classlist[i];
  13. // **重点**
  14. Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
  15. if (newCls != cls && newCls) {
  16. // Class was moved but not deleted. Currently this occurs
  17. // only when the new class resolved a future class.
  18. // Non-lazily realize the class below.
  19. resolvedFutureClasses = (Class *)
  20. realloc(resolvedFutureClasses,
  21. (resolvedFutureClassCount+1) * sizeof(Class));
  22. resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
  23. }
  24. }
  25. }

我们在 readClass 方法调用的前后都下个断点,然后打印输出有什么变化

image.png

可以看到在 readClass 方法调用之后,对 cls 进行了类名的赋值操作。此时类的信息目前仅存储了地址和名称,我们再进去看一下 readClass 的源码

  1. Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
  2. {
  3. const char *mangledName = cls->mangledName();
  4. if (headerIsPreoptimized && !replacing) {
  5. // class list built in shared cache
  6. // fixme strict assert doesn't work because of duplicates
  7. // ASSERT(cls == getClass(name));
  8. ASSERT(getClassExceptSomeSwift(mangledName));
  9. } else {
  10. // 添加类名
  11. addNamedClass(cls, mangledName, replacing);
  12. // 插入哈希表中,即从 mach-o 中把类读取到内存当中
  13. addClassTableEntry(cls);
  14. }
  15. return cls;
  16. }

再进去查看 addNamedClass 的源码

  1. static void addNamedClass(Class cls, const char *name, Class replacing = nil)
  2. {
  3. Class old;
  4. if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
  5. inform_duplicate(name, old, cls);
  6. // getMaybeUnrealizedNonMetaClass uses name lookups.
  7. // Classes not found by name lookup must be in the
  8. // secondary meta->nonmeta table.
  9. addNonMetaClass(cls);
  10. } else {
  11. // 将 name 与 cls 的地址进行映射,并插入到内存当中
  12. NXMapInsert(gdb_objc_realized_classes, name, cls);
  13. }
  14. }

查看 mangledName 方法的源码

  1. const char *mangledName() {
  2. // fixme can't assert locks here
  3. ASSERT(this);
  4. // 这个初始化判断在我们前面分析的 lookuoImp 也有出现过
  5. if (isRealized() || isFuture()) {
  6. // 如果类已经初始化过,则从 ro 中直接获取 name
  7. return data()->ro()->name;
  8. } else {
  9. // 否则从 mach-o 中读取 data 里面的 name
  10. return ((const class_ro_t *)data())->name;
  11. }
  12. }

综上可得:readClass 的主要作用就是将 mach-o 的类读取到内存当中,当前的类中仅有两个信息,即地址和名称,data 数据会在步骤九中读取出来并赋值到类中。

2.2.4 步骤4: 修复重映射一些没有被镜像文件加载进来的类

  1. // Fix up remapped classes
  2. // Class list and nonlazy class list remain unremapped.
  3. // Class refs and super refs are remapped for message dispatching.
  4. if (!noClassesRemapped()) {
  5. for (EACH_HEADER) {
  6. Class *classrefs = _getObjc2ClassRefs(hi, &count);
  7. for (i = 0; i < count; i++) {
  8. remapClassRef(&classrefs[i]);
  9. }
  10. // fixme why doesn't test future1 catch the absence of this?
  11. classrefs = _getObjc2SuperRefs(hi, &count);
  12. for (i = 0; i < count; i++) {
  13. remapClassRef(&classrefs[i]);
  14. }
  15. }
  16. }

主要是将未被映射的 classsuperclass 进行重映射。从注释中可知,被重映射的类都是懒加载类。如果没有在相关的类里面进行相关处理,是不会执行这段代码的。

  • _getObjc2ClassRefs 是从 mach-o 的静态段 __objc_classrefs 中获取类的引用
  • _getObjc2SuperRefs 是从 mach-o 的静态段 __objc_superrefs 中获取父类的引用

2.2.5 步骤5: 修复一些消息

  1. #if SUPPORT_FIXUP
  2. // Fix up old objc_msgSend_fixup call sites
  3. // 5.修复一些消息
  4. for (EACH_HEADER) {
  5. message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
  6. if (count == 0) continue;
  7. if (PrintVtables) {
  8. _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
  9. "call sites in %s", count, hi->fname());
  10. }
  11. for (i = 0; i < count; i++) {
  12. fixupMessageRef(refs+i);
  13. }
  14. }
  15. ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
  16. #endif

主要是通过 _getObjc2MessageRefsmach-o 的静态段 __objc_msgrefs 中获取 message_ref_t,然后通过遍历,从 fixupMessageRef 中将函数指针进行注册,并修复为新的函数指针。

2.2.6 步骤6: 当我们类里面有协议的时候: readProtocol

  1. // Discover protocols. Fix up protocol refs.
  2. // 6.当我们类里面有协议的时候: readProtocol
  3. for (EACH_HEADER) {
  4. // 遍历所有协议列表,并且将协议列表加载到 protocol 的 NXMapTable 表中。
  5. extern objc_class OBJC_CLASS_$_Protocol;
  6. Class cls = (Class)&OBJC_CLASS_$_Protocol;
  7. ASSERT(cls);
  8. // 获取 protocol 的 NXMapTable(哈希表)
  9. NXMapTable *protocol_map = protocols();
  10. bool isPreoptimized = hi->hasPreoptimizedProtocols();
  11. // Skip reading protocols if this is an image from the shared cache
  12. // and we support roots
  13. // Note, after launch we do need to walk the protocol as the protocol
  14. // in the shared cache is marked with isCanonical() and that may not
  15. // be true if some non-shared cache binary was chosen as the canonical
  16. // definition
  17. if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
  18. if (PrintProtocols) {
  19. _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
  20. hi->fname());
  21. }
  22. continue;
  23. }
  24. bool isBundle = hi->isBundle();
  25. // 从 mach-o 的静态段 __objc_protolist 中获取协议列表
  26. protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
  27. // 遍历循环将 protocol 添加到 protocol_map(哈希表) 中
  28. for (i = 0; i < count; i++) {
  29. readProtocol(protolist[i], cls, protocol_map,
  30. isPreoptimized, isBundle);
  31. }
  32. }

2.2.7 步骤7: 修复没有被加载的协议

  1. // Fix up @protocol references
  2. // Preoptimized images may have the right
  3. // answer already but we don't know for sure.
  4. // 7.修复没有被加载的协议
  5. for (EACH_HEADER) {
  6. // At launch time, we know preoptimized image refs are pointing at the
  7. // shared cache definition of a protocol. We can skip the check on
  8. // launch, but have to visit @protocol refs for shared cache images
  9. // loaded later.
  10. if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
  11. continue;
  12. // 从 mach-o 的静态段 __objc_protorefs 中获取 protolist
  13. protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
  14. // 循环遍历 protolist,对当前协议和协议列表中同一地址的协议进行比较,如果协议不同,则用当前协议进行替换。
  15. for (i = 0; i < count; i++) {
  16. remapProtocolRef(&protolist[i]);
  17. }
  18. }
  1. /***********************************************************************
  2. * remapProtocolRef
  3. * Fix up a protocol ref, in case the protocol referenced has been reallocated.
  4. * Locking: runtimeLock must be read- or write-locked by the caller
  5. **********************************************************************/
  6. static size_t UnfixedProtocolReferences;
  7. static void remapProtocolRef(protocol_t **protoref)
  8. {
  9. runtimeLock.assertLocked();
  10. protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
  11. if (*protoref != newproto) {
  12. *protoref = newproto;
  13. UnfixedProtocolReferences++;
  14. }
  15. }

2.2.8 步骤8: 分类处理

  1. // Discover categories. Only do this after the initial category
  2. // attachment has been done. For categories present at startup,
  3. // discovery is deferred until the first load_images call after
  4. // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
  5. // 8.分类的处理
  6. if (didInitialAttachCategories) {
  7. for (EACH_HEADER) {
  8. load_categories_nolock(hi);
  9. }
  10. }

根据注释可知,主要是对分类进行处理,需要在分类初始化并将数据加载到类后才执行,对在运行时才出现的分类,将分类的加载推迟到 _dyld_objc_notify_register 调用完成之后的第一个load_images 调用为止。

2.2.9 步骤9: 类的加载处理

  1. // Realize non-lazy classes (for +load methods and static instances)
  2. for (EACH_HEADER) {
  3. classref_t const *classlist =
  4. _getObjc2NonlazyClassList(hi, &count);
  5. for (i = 0; i < count; i++) {
  6. Class cls = remapClass(classlist[i]);
  7. if (!cls) continue;
  8. addClassTableEntry(cls);
  9. if (cls->isSwiftStable()) {
  10. if (cls->swiftMetadataInitializer()) {
  11. _objc_fatal("Swift class %s with a metadata initializer "
  12. "is not allowed to be non-lazy",
  13. cls->nameForLogging());
  14. }
  15. // fixme also disallow relocatable classes
  16. // We can't disallow all Swift classes because of
  17. // classes like Swift.__EmptyArrayStorage
  18. }
  19. realizeClassWithoutSwift(cls, nil);
  20. }
  21. }

通过注释,我们可以得知,当前只有 类为非懒加载类 的时候,才会执行。

1. 从 mach-o 的静态段 __objc_nlclslist 中获取非懒加载类表。

2. 通过 addClassTableEntry 将非懒加载的类插入到列表中,然后存储到内存当中,其中如果当前类已经被添加,就不会再次进行添加了。

3. 通过 realizeClassWithoutSwift 实现类的 data 的加载,因为前面我们的类的信息目前仅存储了地址和名称。

接下来研究此步骤中的重点部分 realizeClassWithoutSwift 方法

realizeClassWithoutSwift 方法主要作用是将类的 data 信息加载到内存当中,还有对 rorw 进行了相关操作。主要有以下几个步骤。

  • 1. 读取 data 数据,并设置 rorw
    • 读取 data 数据,并将其强转为 ro,以及初始化 rw,然后将 ro 拷贝一份到 rwro
    • ro 表示 read only,其在编译期的时候就已经确定了内存,包含了类的名称、方法列表、协议列表、属性列表和成员变量列表的信息。由于它是只读的,确定了之后就不会发生变化,所以属于 干净的内存(Clean Memory)
    • rw 表示 read Write,由于 OC 的动态性,所以可能会在运行时动态往类中添加属性、方法和协议。
    • rwe 表示 read Write ext,在2020的WWDC上,这部视频 Objective-C运行时的进步内存优化 做了进一步的改进。其中说到 rw 中只有 10% 左右的类真正更改了它们的方法、属性等,所以新增加了 rwe,即是类的额外信息,rwrwe 都属于 脏内存(dirty memory)
  1. // fixme verify class is not in an un-dlopened part of the shared cache?
  2. // 读取 cls 的 data,并将其强转为 ro,以及初始化 rw,然后将 ro 拷贝一份到 rw 的 ro。
  3. // 读取类结构的 bits 属性。
  4. auto ro = (const class_ro_t *)cls->data();
  5. // 判断是否是元类
  6. auto isMeta = ro->flags & RO_META;
  7. if (ro->flags & RO_FUTURE) {
  8. // This was a future class. rw data is already allocated.
  9. rw = cls->data();
  10. ro = cls->data()->ro();
  11. ASSERT(!isMeta);
  12. cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
  13. } else {
  14. // Normal class. Allocate writeable class data.
  15. // 创建 rw
  16. rw = objc::zalloc<class_rw_t>();
  17. // rw 赋值 ro
  18. rw->set_ro(ro);
  19. rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
  20. // 将 rw 赋值给 cls 的 data
  21. cls->setData(rw);
  22. }
  • 2. 递归调用 realizeClassWithoutSwift 完成类的继承链关系。
    • 递归调用 realizeClassWithoutSwift,设置父类和元类的 data 信息加载到内存当中。
    • 分别将父类和元类赋值给 classsuperclassclassIsa
  1. // Realize superclass and metaclass, if they aren't already.
  2. // This needs to be done after RW_REALIZED is set above, for root classes.
  3. // This needs to be done after class index is chosen, for root metaclasses.
  4. // This assumes that none of those classes have Swift contents,
  5. // or that Swift's initializers have already been called.
  6. // fixme that assumption will be wrong if we add support
  7. // for ObjC subclasses of Swift classes.
  8. // 递归调用 `realizeClassWithoutSwift`,设置父类和元类的 `data` 信息加载到内存当中。
  9. // 当 isa 找到根元类之后,因为根元类的 isa 是指向自己的,不会返回 nil 从而导致无限递归,在 remapClass 中对类在表中进行查找的操作,如果表中已有该类,则返回一个空值;如果没有则返回当前类,这样保证了类只加载一次并结束递归
  10. supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
  11. metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
  12. // Update superclass and metaclass in case of remapping
  13. // 分别将父类和元类赋值给 `class` 的 `superclass` 和 `classIsa`。
  14. cls->superclass = supercls;
  15. cls->initClassIsa(metacls);
  16. // Connect this class to its superclass's subclass lists
  17. // 注意:class 是双向链接结构,即父类可以找到子类,子类可以找到父类
  18. if (supercls) {
  19. // 将 cls 作为 supercls 的子类添加。
  20. addSubclass(supercls, cls);
  21. } else {
  22. // 将 cls 添加为新的已实现的根类。
  23. addRootClass(cls);
  24. }
  • 3. 调用 methodizeClass 方法,读取方法列表(包括分类)、协议列表、属性列表,然后赋值给 rw,最后返回 cls
  1. // Attach categories - 附加分类
  2. methodizeClass(cls, previously);
  3. return cls;

2.2.10 步骤10: 没有被处理的类,优化那些被侵犯的类

  1. // Realize newly-resolved future classes, in case CF manipulates them
  2. // 10.实现没有被处理的类,优化那些被侵犯的类
  3. if (resolvedFutureClasses) {
  4. for (i = 0; i < resolvedFutureClassCount; i++) {
  5. Class cls = resolvedFutureClasses[i];
  6. if (cls->isSwiftStable()) {
  7. _objc_fatal("Swift class is not allowed to be future");
  8. }
  9. realizeClassWithoutSwift(cls, nil);
  10. cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
  11. }
  12. free(resolvedFutureClasses);
  13. }

主要是实现没有被处理的类,优化那些被侵犯的类。

2.3 methodizeClass 分析

  1. /***********************************************************************
  2. * methodizeClass
  3. * Fixes up cls's method list, protocol list, and property list.
  4. * Attaches any outstanding categories.
  5. * Locking: runtimeLock must be held by the caller
  6. **********************************************************************/
  7. static void methodizeClass(Class cls, Class previously)
  8. {
  9. runtimeLock.assertLocked();
  10. bool isMeta = cls->isMetaClass();
  11. auto rw = cls->data();
  12. auto ro = rw->ro();
  13. auto rwe = rw->ext();
  14. // Install methods and properties that the class implements itself.
  15. // 将方法列表、属性列表和协议列表赋值到 rwe
  16. // 获取 ro 的 方法列表
  17. method_list_t *list = ro->baseMethods();
  18. if (list) {
  19. // 对方法列表进行排序操作
  20. prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
  21. // 把方法赋值给 rwe 的 method
  22. if (rwe) rwe->methods.attachLists(&list, 1);
  23. }
  24. // 获取 ro 的 属性列表
  25. property_list_t *proplist = ro->baseProperties;
  26. if (rwe && proplist) {
  27. // 把属性赋值给 rwe 的 properties
  28. rwe->properties.attachLists(&proplist, 1);
  29. }
  30. // 获取 ro 的 协议列表
  31. protocol_list_t *protolist = ro->baseProtocols;
  32. if (rwe && protolist) {
  33. // 把协议赋值给 rwe 的 protocols
  34. rwe->protocols.attachLists(&protolist, 1);
  35. }
  36. // Root classes get bonus method implementations if they don't have
  37. // them already. These apply before category replacements.
  38. if (cls->isRootMetaclass()) {
  39. // root metaclass
  40. addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
  41. }
  42. // Attach categories.
  43. // 附加分类方法
  44. if (previously) {
  45. if (isMeta) {
  46. objc::unattachedCategories.attachToClass(cls, previously,
  47. ATTACH_METACLASS);
  48. } else {
  49. // When a class relocates, categories with class methods
  50. // may be registered on the class itself rather than on
  51. // the metaclass. Tell attachToClass to look for those.
  52. objc::unattachedCategories.attachToClass(cls, previously,
  53. ATTACH_CLASS_AND_METACLASS);
  54. }
  55. }
  56. objc::unattachedCategories.attachToClass(cls, cls,
  57. isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
  58. }

以上代码,可以分为

  • 将方法列表、属性列表和协议列表赋值给 rwe
  • 附加分类方法。

2.3.1 rwe 的获取

  1. class_rw_ext_t *ext() const {
  2. return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
  3. }

在上面的源码中我们可以看到 ext() 方法调用了 get_ro_or_rwe() 获取 ro 或者 rwe()

2.3.2 rwe 的逻辑

方法列表 加入到 rwe 的逻辑为例,主要有以下步骤

1. 获取 robaseMethods

2. 通过 prepareMethodLists 方法进行排序;

3. 通过 attachLists 插入。

2.3.2.1 方法如何排序

查看 prepareMethodLists 的源码,发现内部是通过调用 fixupMethodList 来进行排序的

  1. static void
  2. prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
  3. bool baseMethods, bool methodsFromBundle)
  4. {
  5. for (int i = 0; i < addedCount; i++) {
  6. method_list_t *mlist = addedLists[i];
  7. ASSERT(mlist);
  8. // Fixup selectors if necessary
  9. if (!mlist->isFixedUp()) {
  10. // 重点部分
  11. fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
  12. }
  13. }
  14. }

查看 fixupMethodList 的源码,发现内部是根据 SortBySELAddress,即根据 sel 的地址进行排序的。

  1. static void
  2. fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
  3. {
  4. runtimeLock.assertLocked();
  5. ASSERT(!mlist->isFixedUp());
  6. // fixme lock less in attachMethodLists ?
  7. // dyld3 may have already uniqued, but not sorted, the list
  8. if (!mlist->isUniqued()) {
  9. mutex_locker_t lock(selLock);
  10. // Unique selectors in list.
  11. for (auto& meth : *mlist) {
  12. const char *name = sel_cname(meth.name);
  13. meth.name = sel_registerNameNoLock(name, bundleCopy);
  14. }
  15. }
  16. // Sort by selector address.
  17. // 根据 `sel` 的地址进行排序
  18. if (sort) {
  19. method_t::SortBySELAddress sorter;
  20. std::stable_sort(mlist->begin(), mlist->end(), sorter);
  21. }
  22. // Mark method list as uniqued and sorted
  23. mlist->setFixedUp();
  24. }

2.3.2.2 验证方法排序

我们接下来通过断点调试来进行验证

image.png

我们先打印出上面 baseMethodList 的值之后,进入 prepareMethodLists 方法。然后执行到 fixupMethodList

image.png

然后我们在 sort 排序之后,再下一个断点

image.png
image.png

然后我们输出 mlist

image.png

根据我们方法排序前后的对比,可以得到 methodizeClass 实现了 类的方法的序列化

2.3.3 attachToClass 方法

主要是将分类添加到主类中

  1. void attachToClass(Class cls, Class previously, int flags)
  2. {
  3. runtimeLock.assertLocked();
  4. ASSERT((flags & ATTACH_CLASS) ||
  5. (flags & ATTACH_METACLASS) ||
  6. (flags & ATTACH_CLASS_AND_METACLASS));
  7. auto &map = get();
  8. // 找到一个分类就进来一次,防止混乱
  9. auto it = map.find(previously);
  10. // 注意:当主类没有实现 load 方法的时候,分类实现了 load 方法,那么会迫使主类进行加载,所以会进入 if 判断里面
  11. if (it != map.end()) {
  12. category_list &list = it->second;
  13. // 判断是否元类
  14. if (flags & ATTACH_CLASS_AND_METACLASS) {
  15. int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
  16. // 附加实例方法
  17. attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
  18. // 附加类方法
  19. attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
  20. } else {
  21. // 附加实例方法
  22. attachCategories(cls, list.array(), list.count(), flags);
  23. }
  24. map.erase(it);
  25. }
  26. }

2.3.4 attachCategories 方法

主要是准备分类的数据

  1. // Attach method lists and properties and protocols from categories to a class.
  2. // Assumes the categories in cats are all loaded and sorted by load order,
  3. // oldest categories first.
  4. static void
  5. attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
  6. int flags)
  7. {
  8. if (slowpath(PrintReplacedMethods)) {
  9. printReplacements(cls, cats_list, cats_count);
  10. }
  11. if (slowpath(PrintConnecting)) {
  12. _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
  13. cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
  14. cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
  15. }
  16. /*
  17. * Only a few classes have more than 64 categories during launch.
  18. * This uses a little stack, and avoids malloc.
  19. *
  20. * Categories must be added in the proper order, which is back
  21. * to front. To do that with the chunking, we iterate cats_list
  22. * from front to back, build up the local buffers backwards,
  23. * and call attachLists on the chunks. attachLists prepends the
  24. * lists, so the final result is in the expected order.
  25. */
  26. constexpr uint32_t ATTACH_BUFSIZ = 64;
  27. method_list_t *mlists[ATTACH_BUFSIZ];
  28. property_list_t *proplists[ATTACH_BUFSIZ];
  29. protocol_list_t *protolists[ATTACH_BUFSIZ];
  30. uint32_t mcount = 0;
  31. uint32_t propcount = 0;
  32. uint32_t protocount = 0;
  33. bool fromBundle = NO;
  34. bool isMeta = (flags & ATTACH_METACLASS);
  35. // 初始化 rwe,因为要往主类添加方法、属性和协议
  36. auto rwe = cls->data()->extAllocIfNeeded();
  37. // mlists -> 二维数组
  38. for (uint32_t i = 0; i < cats_count; i++) {
  39. auto& entry = cats_list[i];
  40. method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
  41. if (mlist) {
  42. // 当前 mcount = 0,ATTACH_BUFSIZ= 64,所以不会进入 if 里面
  43. if (mcount == ATTACH_BUFSIZ) {
  44. prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
  45. rwe->methods.attachLists(mlists, mcount);
  46. mcount = 0;
  47. }
  48. // 倒序插入
  49. mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
  50. fromBundle |= entry.hi->isBundle();
  51. }
  52. property_list_t *proplist =
  53. entry.cat->propertiesForMeta(isMeta, entry.hi);
  54. if (proplist) {
  55. if (propcount == ATTACH_BUFSIZ) {
  56. rwe->properties.attachLists(proplists, propcount);
  57. propcount = 0;
  58. }
  59. proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
  60. }
  61. protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
  62. if (protolist) {
  63. if (protocount == ATTACH_BUFSIZ) {
  64. rwe->protocols.attachLists(protolists, protocount);
  65. protocount = 0;
  66. }
  67. protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
  68. }
  69. }
  70. if (mcount > 0) {
  71. // 排序
  72. prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
  73. // 进行内存平移操作
  74. rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
  75. if (flags & ATTACH_EXISTING) flushCaches(cls);
  76. }
  77. rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
  78. rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
  79. }

2.3.4.1 extAllocIfNeeded 方法

  1. class_rw_ext_t *extAllocIfNeeded() {
  2. auto v = get_ro_or_rwe();
  3. // 判断 rwe 是否存在
  4. if (fastpath(v.is<class_rw_ext_t *>())) {
  5. // 如果存在,则直接返回 rwe
  6. return v.get<class_rw_ext_t *>();
  7. } else {
  8. // 不存在,则进行开辟
  9. return extAlloc(v.get<const class_ro_t *>());
  10. }
  11. }

2.3.4.1 extAlloc 方法

  1. class_rw_ext_t *
  2. class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
  3. {
  4. runtimeLock.assertLocked();
  5. // 初始化 rwe
  6. auto rwe = objc::zalloc<class_rw_ext_t>();
  7. rwe->version = (ro->flags & RO_META) ? 7 : 0;
  8. method_list_t *list = ro->baseMethods();
  9. if (list) {
  10. if (deepCopy) list = list->duplicate();
  11. rwe->methods.attachLists(&list, 1);
  12. }
  13. // See comments in objc_duplicateClass
  14. // property lists and protocol lists historically
  15. // have not been deep-copied
  16. //
  17. // This is probably wrong and ought to be fixed some day
  18. property_list_t *proplist = ro->baseProperties;
  19. if (proplist) {
  20. rwe->properties.attachLists(&proplist, 1);
  21. }
  22. protocol_list_t *protolist = ro->baseProtocols;
  23. if (protolist) {
  24. rwe->protocols.attachLists(&protolist, 1);
  25. }
  26. set_ro_or_rwe(rwe, ro);
  27. return rwe;
  28. }

2.3.5 attachLists 方法

  1. void attachLists(List* const * addedLists, uint32_t addedCount) {
  2. if (addedCount == 0) return;
  3. if (hasArray()) {
  4. // many lists -> many lists
  5. // 获取数组中旧 lists 的大小
  6. uint32_t oldCount = array()->count;
  7. // 计算新的容量大小 = 旧数据大小 + 新数据大小
  8. uint32_t newCount = oldCount + addedCount;
  9. // 根据新的容量大小,开辟一个数组,类型是 array_t,通过 array() 获取
  10. setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
  11. // 设置数组的大小
  12. array()->count = newCount;
  13. // 旧的数据从 addedCount 数组下标开始存放旧的 lists,大小 = 旧数据大小 * 单个旧 list 大小
  14. memmove(array()->lists + addedCount, array()->lists,
  15. oldCount * sizeof(array()->lists[0]));
  16. // 新数据从数组 首位置开始存储,存放新的lists,大小 = 新数据大小 * 单个list大小
  17. memcpy(array()->lists, addedLists,
  18. addedCount * sizeof(array()->lists[0]));
  19. }
  20. else if (!list && addedCount == 1) {
  21. // 0 lists -> 1 list
  22. // 将 list 加入 mlists 的第一个元素,此时的 list 是一个一维数组
  23. list = addedLists[0];
  24. }
  25. else {
  26. // 新的 list 就是分类,来自LRU的算法思维,即最近最少使用算法
  27. // 获取旧的list
  28. List* oldList = list;
  29. uint32_t oldCount = oldList ? 1 : 0;
  30. // 计算容量和 = 旧list个数 + 新lists的个数
  31. uint32_t newCount = oldCount + addedCount;
  32. // 开辟一个容量和大小的集合,类型是 array_t,即创建一个数组,放到 array 中,通过 array() 获取
  33. setArray((array_t *)malloc(array_t::byteSize(newCount)));
  34. // 设置数组的大小
  35. array()->count = newCount;
  36. // 判断old是否存在,如果存在,就将旧的 list 放入到数组的末尾
  37. if (oldList) array()->lists[addedCount] = oldList;
  38. // memcpy(起始位置,放什么,放多大) 内存平移,从数组起始位置存入新的list
  39. //其中 array()->lists 表示首位元素位置
  40. memcpy(array()->lists, addedLists,
  41. addedCount * sizeof(array()->lists[0]));
  42. }
  43. }

从上可知,插入表可分为以下三种情况

  • 1. 多对多: 如果当前调用 attachListslist_array_tt 二维数组中有多个一维数组。
    • 1.1 先计算数组中旧 list 的大小
    • 1.2 计算新的容量大小 = 旧数据大小 + 新数据大小
    • 1.3 根据新的容量大小,开辟一个数组,类型是 array_t,通过 array() 获取
    • 1.4 设置数组大小
    • 1.5 旧的数据从 addedCount 数组下标开始 存放旧的 lists,大小 = 旧数据大小 * 单个旧list大小,即指针偏移
    • 1.6 新数据从数组首位置开始存储,存放新的 lists,大小 = 新数据大小 * 单个list大小,可以理解越晚加进来的越在前面,越在前面,调用时则优先调用
  • 2. 零对一: 如果当前调用 attachListslist_array_tt 二维数组为空且新增大小为1。
    • 2.1 直接赋值给 addedList 的第一个 list
  • 3. 一对多: 如果当前调用 attachListslist_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 指的就是我们的分类,所以这就是为什么我们经常碰到的 子类实现父类方法会把父类方法覆盖、分类实现主类方法会把主类方法覆盖的原因了。 这个操作来自于 LRU缓存算法思维,即最近最少使用算法,因为如果子类或者分类覆盖了实现方法的话,那么大概率是因为这个方法是使用频率和价值更高的,要 优先调用。 如果对于 LRU缓存算法 不怎么了解的同学,可以参考 缓存淘汰算法 LRU 和 LFU

3、懒加载类和非懒加载类的数据加载时机

image.png

4、总结

  • readClass 主要是读取类,即此时的类仅有地址和名称,还没有 data 数据。
    • addNamedClass 方法当前类添加到已经创建好的 gdb_objc_realized_classes 哈希表中
    • addClassTableEntry 将非懒加载的类插入到列表中,然后存储到内存当中,其中如果当前类已经被添加,就不会再次进行添加了
  • realizeClassWithoutSwift 主要是实现类,即将类的 data 数据读取到内存中。
    • methodizeClass 方法中实现类的方法、属性、协议的序列化
    • attachCategories 方法中实现类以及分类的数据加载

image.png