上篇文章讲到 dyld 加载动态库时,会调用 notifyObjCInit 函数,去告诉 objc 调用 +load 办法,经过 _dyld_objc_notify_register 与 libobjc 中的 _objc_init 进行通信,其间有三个个参数:map_images 、load_images 和 unmap_image, 其间 unmap_image 是在镜像消失时才调用,本篇文章就来探究一下 map_images 和load_images。
1. _objc_init
去 OC 源码里边查看一下该函数:
-
environ_init(): 环境变量初始化,能够在Edit Scheme -> Arguments增加一些环境变量 -
tls_init(): 创建线程的析构函数,处理线程 key 的绑定 -
static_init(): 运行 c++ 静态构造函数 -
runtime_init(): 初始化两张表:分类的表和类的表 -
exception_init(): 异常处理的初始化 -
didCallDyldNotifyRegister: 标识对_dyld_objc_register的调用已完成
2. map_images
现在来探究一下 map_images:
因为 map_images_nolock 函数代码比较多,咱们分段看下:
1. preopt_init 初始化环境
这一段最首要的函数便是当一次调用该函数时,会调用 preopt_init 来准备初始化环境
能够发现 preopt_init 效果首要便是初始化一些同享缓存,包含选择器的缓存、头的缓存、类的缓存、协议的缓存等
2. 获取并增加 image 的 header 指针
大致步骤如下:
- 首先获取
image的header指针,将mach_header的指针转换成headerType类型的指针 - 经过
addHeader获取header_info, 并将header_info刺进到链表里 -
addHeader办法先从同享缓存中拿,假如有,就刺进链表并回来 -
addHeader办法假如没有从同享缓存中拿到,就会封装一个header_info,然后再刺进链表并回来。
3. sel_init 和 arr_init
能够看到在第一次履行 map_images,会调用 sel_init 和 arr_init:
sel_init 首要是初始化 selector 表,该表定义如下:
namedSelectors 是个大局变量,存储一切的办法名 SEL,内部结构是 hash 表 DenseMap
能够看出 arr_init 首要做了以下几件事:
- 初始化自动释放池
AutoreleasePool -
SideTablesMap初始化 -
AssociationsManager的初始化,即为大局运用的相关目标表开辟空间,关于相关目标,能够看下篇文章
4. _read_images
发现里边有很多代码,这也是 map_images 的中心所在
1. 初始化类表
经过注释有以下定论:
-
doneOnce保证了_read_images只履行一次 -
gcd_objc_realized_classes是一个大局的类表,只需class没有在同享缓存中,那么不论其有没有完成都会存在这个类表里,其本质是个hash表
2. rebase
_getObjc2SelectorRefs 便是拿到 Mach-O 的静态段 __obj_selrefs, 后面一切经过 _getObjc2 开头的 Mach-O 静态段获取,都对应不同的 section name, 如下:
这段代码首要的效果便是将一切的 SEL 注册到 namedSelectors 表中,且当 _getObjc2SelectorRefs 中得到的 SEL 和 sel_registerNameNoLock 中的 SEL 不一起,就会把前者的 SEL 批改修正成后者,这一步便是 rebase, 修正镜像内部的资源指针。验证一下:
形成这两个函数地址不同的原因是 ALSR 偏移
3. 读取类
这一步的首要效果便是发现并读取类,readClass 是要害函数,在未调用该办法前,cls 仅仅一个地址,履行该办法后,cls 存入表中,是一个类的称号
能够看到有很多条件判断,那么咱们自己加段代码,进行单步调试:
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
const char *userClassName = "User";
auto user_ro = (const class_ro_t *)cls->data();
// 判断是否是元类
auto user_isMeta = user_ro->flags & RO_META;
if (strcmp(mangledName, userClassName) == 0 && !user_isMeta) {
// do something
}
...
}
打上断点,进行调试,发现 readClass 首要做了以下几件事:
-
addNamedClass, 该函数把name和cls增加到命名为gdb_objc_realized_classes的表中去,在addNamedClass办法中,调用NXMapInsert把cls和name刺进到NXMapTable中,NXMapTable为hash表 -
addClassTableEntry将类和元类加入到allocatedClasses表中
4. 修正类、音讯、协议
这儿的首要效果便是将未映射的 class 和 superclass 进行重映射
-
_getObjc2ClassRefs是获取Mach-O中的静态段__objc_classrefs, 即类的引证 -
_getObjc2SuperRefs是获取Mach-O中的静态段__objc_superrefs, 即父类的引证 -
fixupMessageRef修正一些音讯的调用 - 当类里边有协议时,经过
_getObjc2ProtocolList获取到Mach-O中的静态段__objc_protolist协议列表,即从编译器读取并初始化protocol - 修正没有被加载到的协议,经过
_getObjc2ProtocolRefs获取Mach-O的静态段__objc_protorefs, 然后遍历需求修正的协议,经过remapProtocolRef比较当时协议和协议列表中的同一个内存地址的协议是否相同,假如不同则替换
5. 分类的处理
下一篇文章处理
6. 非懒加载类的加载
非懒加载类和懒加载类的差异便是是否完成了 +load 办法
- 完成了
+load办法,便是非懒加载类 - 没完成便是懒加载类,因为
load会被提前加载,load办法会在load_images中调用
- 在
hi->nlclslist(&count)中经过_getObjc2NonlazyClassList获取Mach-O的静态段__objc_nlclslist非懒加载类表 -
addClassTableEntry将非懒加载类刺进类表,存储到内存,假如已经增加就不会再增加,需求确保整个结构都被增加 -
realizeClassWithoutSwift完成当时类,因为之前readClass读取到内存的只有地址和称号,类的data数据并没有加载出来。realizeClassWithoutSwift下一篇文章处理
7. 没有被处理的类
8. read_images 总结
- 加载一切的类到类的
gdb_objc_realized_classes表中 - 对一切类做重映射
- 将一切
SEL都注册到namedSeletors表中 - 修正函数指针
- 将一切
Protocol都增加到protocol_map表中 - 对一切
Protocol做重映射 - 初始化一切非懒加载类,对
rw、ro等进行操作(初始化) - 遍历已标记的懒加载类,并做初始化操作
- 处理一切
Category, 包含Class和Meta Class - 初始化一切未初始化的类
3. load_images
经过源码来看 load_images 首要做了以下几件事:
-
loadAllCategories加载一切分类,分类的加载下一篇文章处理 -
prepare_load_methods找到load办法 -
call_load_methods调用load办法
1. prepare_load_methods
- 这儿的
schedule_class_load会进行一个递归调用,在找load办法时,会优先找父类的办法,然后再把找到的办法增加到loadable_classes表里边去 -
add_class_to_loadable_list会去找分类的load办法,找到之后同样会被增加到loadable_classes表里边去 load办法不是走音讯派发机制的,而是经过地址调用
2. call_load_methods
3. load 办法总结
- 子类的
load办法默许完成了父类的父类的load办法,所以不需求写super load -
load办法履行次序:父类 > 本类 > 分类 -
load办法内部运用了锁,所以时线程安全的 - 有多个类别完成了
load办法时,load办法都会履行,履行次序与编译次序有关(在Build Phases -> Compile Sources里边查看编译次序),后编译的先履行
拓宽:环境变量的配置
在 _objc_init 办法中,能够看到会对环境变量进行初始化,以环境变量 OBJC_DISABLE_NONPOINTER_ISA 为例,该环境变量为是否敞开指针优化,YES 表明纯指针,NO 就表明运用 nonpointerisa:
- 未设置 OBJC_DISABLE_NONPOINTER_ISA 时,目标的 isa 指针地址结尾为 1,默许敞开了指针优化,表明 isa 不仅包含了类目标地址,还包含了类信息、目标的引证计数等
- 设置 OBJC_DISABLE_NONPOINTER_ISA 为 YES 后,isa 地址结尾变成了 0,此时的 isa 就表明类的首地址
其他的一些环境变量及阐明:
