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

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

一、资料准备

  1. objc4-818.2

对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。

二、思路

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

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

三、寻找map_images源码的核心

1. 找到map_images

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

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

因为map_images内容很多,也很重要,执行代码也耗时。

如果dyld在执行的过程中,由于某些原因,导致map_images函数的地址发生了变化,那么使用&进行指针传递,就可以保证dyld可以正确调用到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

ini 复制代码
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的相关内容

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

答 : 进入_getObjc2SelectorRefs源码 :

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

  1. 插入到namedSelectors哈希表是怎么回事?

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

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

3. 类的一些处理

  1. 类的处理是一个重点,下一章单独开一章了解readClass的源码。
  2. 举例 : 在Class cls = (Class)classlist[i];这句代码处挂上断点。运行程序

可以看到此时的cls只有地址。如果断点换到了readClass执行之后,类则有了名称和地址 :

4. 修复,重新映射一些没有加载到内存的类和父类

不是重点。一般都进不来if判断。

5. 老版本的objc_msgSend修复(兼容老版本)

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

7. 修复,重新映射一些没有加载到内存的协议

8. 分类的处理

9. 非懒加载类的实现,或者说非懒加载类的内容加载

10. 处理未被处理的类

五、总结

映射镜像也就是map_images的核心是读取镜像(_read_images)。

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

  1. 修复sel的地址。让sel的地址变成真正的内存中的地址。
  2. 类的基本处理。在内存中保存了类的名称和地址,但是没有类的其他内容。
  3. 重新映射那些没有被成功映射的类和父类。
  4. 兼容老版本的objc_msgSend。
  5. 如果有协议,则读取协议。
  6. 重新映射那些没有被成功映射的协议。
  7. 分类的处理。分类的处理要推迟到_dyld_objc_notify_register完成之后,第一次进行load_images(加载镜像)时再发现分类
  8. 非懒加载类的实现,也就是对非懒加载类的内容(不止是2中的名称和地址)进行加载。
  9. 处理未被处理的类。
相关推荐
crasowas4 小时前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best6 小时前
ios按键精灵脚本开发:ios悬浮窗命令
ios
Code&Ocean11 小时前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/11 小时前
Laya ios接入goole广告,开始接入 2
ios
恋猫de小郭1 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
网安墨雨1 天前
iOS应用网络安全之HTTPS
web安全·ios·https
福大大架构师每日一题1 天前
37.1 prometheus管理接口源码讲解
ios·iphone·prometheus
BangRaJun2 天前
LNCollectionView-替换幂率流体
算法·ios·设计
刘小哈哈哈2 天前
iOS 多个输入框弹出键盘处理
macos·ios·cocoa
靴子学长2 天前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui