本文由快学吧个人写作,以任何方式转载请表明原文出处

一、资料准备

  1. objc4-818.2

对应mac的版别是11.1。可根据自己的体系版别挑选能够进行调试的源码。

二、思路

  1. 此章和十七、十八两章是有关的,怎么找到了map_images,都在前两章。

  2. map_images,通过名字知道也知道,它是映射镜像的函数,那么它做了什么,需要映射什么内容到内存上给app运用?咱们写的代码是否是在这里被映射到了内存上?

三、寻觅map_images源码的中心

1. 找到map_images

在送给_objc_init()也便是objc库的初始化函数中,是由dyld的回调函数来真正履行的。

十九、app的加载流程(三)map_images映射镜像

2. 为什么map_images有&取地址符?

其实有没有都相同,由于函数称号本身便是个地址,传递函数名,实质上传递的便是函数的地址。

3. map_images源码

1. 官方注释 : map_images函数是用来处理dyld正在映射的镜像。

十九、app的加载流程(三)map_images映射镜像

2. map_images_nolock

源码很长,我的主要意图是找被映射的objc镜像都做了什么,所以找到了最终的,读取镜像:_read_images

十九、app的加载流程(三)map_images映射镜像

另外看一点,是下面2. 修正@selector的相关内容中会用到的namedSelectors这个哈希表怎么来的,源码也在这里。

十九、app的加载流程(三)map_images映射镜像

十九、app的加载流程(三)map_images映射镜像

3. _read_images

读取镜像是映射镜像的要点。源码很长,下面先进行大的思路的收拾。

十九、app的加载流程(三)map_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)的判别完毕。
  1. #define EACH_HEADER是遍历头文件,从_read_images源码的开始到完毕才有#undef EACH_HEADER。这说明读取镜像文件,是遍历读取的。
  2. doneOnce的判别语句中的代码,只要读取第一个头文件的镜像的时分,才会履行。以后都不会履行if中的代码。
  3. 唯一一次履行if,创立了一个哈希表,用来存储dyld的同享缓存中没有进行缓存的类,不管这个类是否完结过。

2. 修正@selector的相关内容

十九、app的加载流程(三)map_images映射镜像

  1. 为什么_getObjc2SelectorRefs是获取mach-o静态段中的__objc_selrefs内容?为什么sels是一个SEL *这样的指针类型?

答 : 进入_getObjc2SelectorRefs源码 :

十九、app的加载流程(三)map_images映射镜像

所以这个函数是取mach-o文件中的_getObjc2SelectorRefs的所有字段内容,不单单是一个sel,而是许多。所以需要用SEL *来获取mach-o中的整个__objc_selrefs的地址。

  1. 刺进到namedSelectors哈希表是怎么回事?

答 : 进入sel_registerNameNoLock源码,只封装了一行代码,再进入封装的这个__sel_registerName :

十九、app的加载流程(三)map_images映射镜像

  1. 举例 : 在sels[i] = sel;这行源码打上断点,运转objc818.2 :

十九、app的加载流程(三)map_images映射镜像

3. 类的一些处理

十九、app的加载流程(三)map_images映射镜像

  1. 类的处理是一个要点,和类的加载有关,下一章独自开一章类的加载,来了解readClass的源码。
  2. 举例 : 在Class cls = (Class)classlist[i];这句代码处挂上断点。运转程序

十九、app的加载流程(三)map_images映射镜像

能够看到此刻的cls只要地址。如果断点换到了readClass履行之后,类则有了称号和地址 :

十九、app的加载流程(三)map_images映射镜像

4. 修正,从头映射一些没有加载到内存的类和父类

不是要点。一般都进不来if判别。

十九、app的加载流程(三)map_images映射镜像

5. 老版别的objc_msgSend修正(兼容老版别)

十九、app的加载流程(三)map_images映射镜像

6. 如果类里边有协议,读取协议

十九、app的加载流程(三)map_images映射镜像

7. 修正,从头映射一些没有加载到内存的协议

十九、app的加载流程(三)map_images映射镜像

8. 分类的处理

十九、app的加载流程(三)map_images映射镜像

9. 非懒加载类的完结,或者说非懒加载类的内容加载

十九、app的加载流程(三)map_images映射镜像

10. 处理未被处理的类

十九、app的加载流程(三)map_images映射镜像

五、总结

映射镜像也便是map_images的中心是读取镜像(_read_images)。

读取镜像源码的主思路流程 :

  1. 修正sel的地址。让sel的地址变成真正的内存中的地址。
  2. 类的根本处理。在内存中保存了类的称号和地址,可是没有类的其他内容。
  3. 从头映射那些没有被成功映射的类和父类。
  4. 兼容老版别的objc_msgSend。
  5. 如果有协议,则读取协议。
  6. 从头映射那些没有被成功映射的协议。
  7. 分类的处理。分类的处理要推迟到_dyld_objc_notify_register完结之后,第一次进行load_images(加载镜像)时再发现分类
  8. 非懒加载类的完结,也便是对非懒加载类的内容(不止是2中的称号和地址)进行加载。
  9. 处理未被处理的类。