本文基于
dyld-832.7.3与objc4-818.2源码
前言
在上一文iOS应用启动(二):环境配置与runtime初始化我们提到了 _dyld_objc_notify_register,本文就将深入对镜像文件的读取map_images与加载loadClass进行解读。
一、 _dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image);

- 这里有两个重要的函数:
- map_images
- 管理文件中和动态库中的所有符号:
classprotocolselectorcategory
- 管理文件中和动态库中的所有符号:
- load_images
- 执行
+load方法
- 执行
- map_images
- 进行了镜像文件的读取与加载
这里有个一个小细节
我们看到这里调用 &map_images 这里用了函数的地址,因为这里非常重要,为了保持整个过程的同步。
二、 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**********************************************************************/voidmap_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.1 map_images_nolock
voidmap_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]){ ...
if (hCount > 0) { _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); }
...
// Call image load funcs after everything is set up. for (auto func : loadImageFuncs) { for (uint32_t i = 0; i < mhCount; i++) { func(mhdrs[i]); } }}2.2 _read_images
这里的源码非常长,但是
Apple贴心的加了Log我们可以从Log找下线索
ts.log("IMAGE TIMES: fix up selector references");
ts.log("IMAGE TIMES: discover classes");
ts.log("IMAGE TIMES: remap classes");
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
ts.log("IMAGE TIMES: discover protocols");
ts.log("IMAGE TIMES: fix up @protocol references");
ts.log("IMAGE TIMES: discover categories");
ts.log("IMAGE TIMES: realize non-lazy classes");
ts.log("IMAGE TIMES: realize future classes");- 整体流程:
- 条件控制进行第一次加载
- 修复预编译阶段的
@selector混乱问题 - 错误混乱的类处理
- 修复重映射一些没有被镜像文件加再进来的类
- 修复一些消息
- 当类中有协议的时候,
readProtocol - 修复没有被加载的协议
- 处理分类
- 类的加载相关处理
修复:主要是指对地址进行修复,将
MachO中的地址修复为dyld处理后的地址
2.3 循环调用 readClass 加载类
在这里添加Log可以发现,自定义的类是最后加载的:
... // 一系列系统类加载readClass -- RYModelreadClass -- RYSubModel这里如果想过滤系统的可以参考下面的代码:
const char *myClassName = "RYModel";const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, myClassName) == 0) { printf("%s -- %s\n", __func__, myClassName);}2.4 readClass 源码中的坑
下图中的代码,看似进行了Class的ro rw的操作但实际运行断点的时候根本不会执行,千万不要被源码误导了。

如图三个断点:

跳过了中间的断点

2.5 寻找类相关的流程
在源码中找到了可能的地方,添加断点:

实现
非懒加载类(实现类+load方法或者静态实例)
Realize non-lazy classes (for +load methods and static instances)

Realize newly-resolved future classes
运行后只输出了:
Realize non-lazy classes _read_images -- RYModel而在其中也发现来到了另一个重要的流程:realizeClassWithoutSwift
关于这部分内容之前在研究msgSend的过程中有顺便进行了探索,可以在类的实现与初始化一文中进行深入了解。
补充:懒加载类 & 非懒加载类
在2.5中提到了 非懒加载类,那么什么是 非懒加载类,什么是 懒加载类?
懒加载类
数据加载推迟到第一次接收第一次消息的时候
非懒加载类
map_image 的时候加载所有类数据。
三、 load_images
- 这里流程就比较简单了
- 加载分类
- 查找所有 load 方法
- 调用 load 方法
voidload_images(const char *path __unused, const struct mach_header *mh){ if (!didInitialAttachCategories && didCallDyldNotifyRegister) { didInitialAttachCategories = true; // 加载分类 loadAllCategories(); }
// Return without taking locks if there are no +load methods here. if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods // 查找所有 load 方法 { mutex_locker_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); }
/// 调用 load 方法 // Call +load methods (without runtimeLock - re-entrant) call_load_methods();}流程图
