本文由快学吧个人写作,以任何方式转载请表明原文出处
一、资料准备
- objc4-818.2
对应mac的版别是11.1。可根据自己的体系版别挑选能够进行调试的源码。
二、思路
-
此章和十七、十八两章是有关的,怎么找到了map_images,都在前两章。
-
map_images,通过名字知道也知道,它是映射镜像的函数,那么它做了什么,需要映射什么内容到内存上给app运用?咱们写的代码是否是在这里被映射到了内存上?
三、寻觅map_images源码的中心
1. 找到map_images
在送给_objc_init()
也便是objc库的初始化函数中,是由dyld的回调函数来真正履行的。
2. 为什么map_images有&
取地址符?
其实有没有都相同,由于函数称号本身便是个地址,传递函数名,实质上传递的便是函数的地址。
3. map_images源码
1. 官方注释 : map_images函数是用来处理dyld正在映射的镜像。
2. map_images_nolock
源码很长,我的主要意图是找被映射的objc镜像都做了什么,所以找到了最终的,读取镜像:_read_images
。
另外看一点,是下面2. 修正@selector的相关内容
中会用到的namedSelectors
这个哈希表怎么来的,源码也在这里。
3. _read_images
读取镜像是映射镜像的要点。源码很长,下面先进行大的思路的收拾。
四、_read_images
注 : 怎么看_read_images
看官方注释,有注释的一般会比较重要。然后找咱们了解的点,开发常常接触的东西来看。
由于源码太长,有的合适粘贴源码的,就粘贴源码在下面,能够用图片的,直接截图源码的图片,图片里有注释。
1. 变量定义,遍历头文件,是否第一次进入_read_images
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
bool launchTime = NO;
TimeLogger ts(PrintImageTimes);
runtimeLock.assertLocked();
// 遍历每一个头文件,都要履行下面的源码
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// doneOnce是表明if里的代码只履行一次
// 也便是说,不是每一个头文件履行下面代码的时分都要进if里边,只要第一个头文件才会进
// 由于doneOnce在上面定义的时分是static,是局部静态变量
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
...
疏忽的源码
...
// namedClasses
// Preoptimized classes don't go in this table.
// 预先优化的类不在这个表中
// 4/3 is NXMapTable's load factor
// 4/3是NXMapTable的负载系数,也便是说,创立表的时分,创立的大小是实际需求容量的4/3
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//这是一个NXMapTable类型的变量,是个表。
//点进去有官方注释说 : 命名呈现了失误,实际上这个表是用来存储没有在dyld同享缓存里边缓存的类,不管这个类是否完结过
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
} //注释 : if(doneOnce)的判别完毕。
-
#define EACH_HEADER
是遍历头文件,从_read_images
源码的开始到完毕才有#undef EACH_HEADER
。这说明读取镜像文件,是遍历读取的。 - doneOnce的判别语句中的代码,只要读取第一个头文件的镜像的时分,才会履行。以后都不会履行if中的代码。
- 唯一一次履行if,创立了一个哈希表,用来存储dyld的同享缓存中没有进行缓存的类,不管这个类是否完结过。
2. 修正@selector的相关内容
- 为什么
_getObjc2SelectorRefs
是获取mach-o静态段中的__objc_selrefs
内容?为什么sels是一个SEL *
这样的指针类型?
答 : 进入_getObjc2SelectorRefs
源码 :
所以这个函数是取mach-o文件中的_getObjc2SelectorRefs
的所有字段内容,不单单是一个sel,而是许多。所以需要用SEL *
来获取mach-o中的整个__objc_selrefs
的地址。
- 刺进到namedSelectors哈希表是怎么回事?
答 : 进入sel_registerNameNoLock
源码,只封装了一行代码,再进入封装的这个__sel_registerName
:
- 举例 : 在
sels[i] = sel;
这行源码打上断点,运转objc818.2 :
3. 类的一些处理
- 类的处理是一个要点,和类的加载有关,下一章独自开一章类的加载,来了解
readClass
的源码。 - 举例 : 在
Class cls = (Class)classlist[i];
这句代码处挂上断点。运转程序
能够看到此刻的cls只要地址。如果断点换到了readClass
履行之后,类则有了称号和地址 :
4. 修正,从头映射一些没有加载到内存的类和父类
不是要点。一般都进不来if判别。
5. 老版别的objc_msgSend修正(兼容老版别)
6. 如果类里边有协议,读取协议
7. 修正,从头映射一些没有加载到内存的协议
8. 分类的处理
9. 非懒加载类的完结,或者说非懒加载类的内容加载
10. 处理未被处理的类
五、总结
映射镜像也便是map_images的中心是读取镜像(_read_images)。
读取镜像源码的主思路流程 :
- 修正sel的地址。让sel的地址变成真正的内存中的地址。
- 类的根本处理。在内存中保存了类的称号和地址,可是没有类的其他内容。
- 从头映射那些没有被成功映射的类和父类。
- 兼容老版别的objc_msgSend。
- 如果有协议,则读取协议。
- 从头映射那些没有被成功映射的协议。
- 分类的处理。分类的处理要推迟到_dyld_objc_notify_register完结之后,第一次进行load_images(加载镜像)时再发现分类
- 非懒加载类的完结,也便是对非懒加载类的内容(不止是2中的称号和地址)进行加载。
- 处理未被处理的类。