iOS 底层探索篇 ——类的加载原理(上)

iOS 底层探索篇 ——类的加载原理(上)

    • 1. objc_init 做了什么
      • 1.1 environ_init
      • 1.2 tls_initz
      • 1.3 static_init
      • 1.4 runtime_init
      • 1.5 exception_init
      • 1.6 cache_init
      • 1.7 _imp_implementationWithBlock_init
      • 1.8 _dyld_objc_notify_register
    • 2. map_images
    • 3. _read_images

1. objc_init 做了什么

上文说到了objc_init调用了_dyld_objc_notify_register,初始化了dyld 里面的sNotifyObjCMappedsNotifyObjCInitsNotifyObjCUnmapped函数并对 sNotifyObjCMappedsNotifyObjCInit直接进行了调用,那么objc_init还做了什么呢?
iOS 底层探索篇 ——类的加载原理(上)_第1张图片

1.1 environ_init

首先看到environ_init,这里面主要做了环境变量的初始化。那么环境变量有什么用呢?先把环境变量打印出来。
iOS 底层探索篇 ——类的加载原理(上)_第2张图片
然后运行,发现有很多的环境变量,其中有OBJC_DISABLE_NONPOINTER_ISAOBJC_PRINT_LOAD_METHODS
iOS 底层探索篇 ——类的加载原理(上)_第3张图片
在这里插入图片描述

NONPOINTER_ISA就是不纯净的isa,里面不仅包含了类的信息,还有其他的一些信息。把 OBJC_DISABLE_NONPOINTER_ISA设为YES,那么就不会有NONPOINTER_ISA,就会得到一个纯净的只包含类的信息的isa。
这个是没有将OBJC_DISABLE_NONPOINTER_ISA设为true之前的isa,可以看到里面包含有其他的信息,因为类信息在x86_64架构里是只有44位的,而这里不止44位有值。并且直接po isa 是得不到类的信息的。
在这里插入图片描述
在这里插入图片描述

点开editScheme,然后在Arguments里面的Environment variables 添加OBJC_DISABLE_NONPOINTER_ISA并设为YES。打勾后运行,可以看到这里是只占用中间一些,最后一位不为1。而且直接po isa 可以得到类的信息。
iOS 底层探索篇 ——类的加载原理(上)_第4张图片
OBJC_PRINT_LOAD_METHODS就是会把调用load方法的地方打印出来,添加OBJC_PRINT_LOAD_METHODS环境变量并且设为YES。
iOS 底层探索篇 ——类的加载原理(上)_第5张图片
运行一下。
iOS 底层探索篇 ——类的加载原理(上)_第6张图片
环境变量也可以通过中在终端中输入export OBJC_HELP=1来查看。在终端中输入export OBJC_HELP=1 后在输入ls,可以看到环境变量在终端中被打印出来了。
iOS 底层探索篇 ——类的加载原理(上)_第7张图片

1.2 tls_initz

environ_init看完,下一个就是tls_init, tls_initz做的就是设置objc的预定义的线程特定键和键的析构函数,来存储objc的私有数据。
iOS 底层探索篇 ——类的加载原理(上)_第8张图片

1.3 static_init

再往下就是static_init,static_init的作用就是调用全局静态c++函数(objc库里面的)。因为这里已经开启了runtime的下层,这里自己调用c++函数是为了方便所有的环境能够准备充分。
iOS 底层探索篇 ——类的加载原理(上)_第9张图片

1.4 runtime_init

接下来就是runtime_init。这里进行了两个表的初始化。
iOS 底层探索篇 ——类的加载原理(上)_第10张图片

1.5 exception_init

在往下就是exception_init,这里做的是初始化libobjc的异常处理系统。
在这里插入图片描述

1.6 cache_init

往下是cache_init,这里做的是缓存条件初始化。

1.7 _imp_implementationWithBlock_init

继续往下就是_imp_implementationWithBlock_init,这里启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。

1.8 _dyld_objc_notify_register

在往下就是重点: _dyld_objc_notify_register(&map_images, load_images, unmap_image)
为什么这里的map_images加了&符号,而其他的没有呢?加了&之后,map_images就会和内部同步发生变化,相当于进行了指针传递。为什么单独map_images加&呢,因为map_images太重要了,map_images是一个比较耗时的过程,如果不同步发生变化,那么就会发生错乱,所以需要一起变化。比如map_images原先的地址是0x100001,但是可能在某个地方发生变化成了0x100000,那么在dyld里面调用map_images的地方也就是registerObjCNotifiers会调用0x100000而不是0x100001了。

2. map_images

看一下map_images的实现。
iOS 底层探索篇 ——类的加载原理(上)_第11张图片

在点击map_images_nolock查看。这里重要的是寻找image的读取和映射,这里找到了_read_images这个方法。

3. _read_images

iOS 底层探索篇 ——类的加载原理(上)_第12张图片
点击_read_images,然后看到代码行数太多。接下来就从整体来看这个方法,把代码块收起来。可以看到这里有log记录每个方法做了些什么。
iOS 底层探索篇 ——类的加载原理(上)_第13张图片

read_images流程:

  • 条件控制进行一次的加载
  • 修复预编译阶段的 @selector 的混乱问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当我们类里面有协议的时候 : readProtocol
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类 优化那些被侵犯的类

首先看条件控制进行一次的加载,这里是对一些环境变量的处理。
iOS 底层探索篇 ——类的加载原理(上)_第14张图片
这里报出一些异常。
iOS 底层探索篇 ——类的加载原理(上)_第15张图片
接下来是taggedPointers的一些处理。
iOS 底层探索篇 ——类的加载原理(上)_第16张图片
在往下就是第一次的重点,看到这里创建了一个表且大小 * 4 / 3, 这是为什么呢?假设现在的总容积为9, 那么要开辟的内存就是9 * 4 / 3 = 12。当存入的时候,就不能超过 12的 3/4 也就是9,所以当开辟内存的时候总容积要乘以 4 / 3。这里还有一个表gdb_objc_realized_classes,这个表是干什么的呢?
iOS 底层探索篇 ——类的加载原理(上)_第17张图片
点击这个表进去,看到注释写着这是一个储存没有在dyld shared cache 里面的class 的表。
iOS 底层探索篇 ——类的加载原理(上)_第18张图片
再来看修复预编译阶段的 @selector 的混乱问题,sel是 名字+地址,那么就有可能出现名字相同但是地址不同的情况,因为sel在每个镜像文件中的地址不同。打个断点运行一下,
iOS 底层探索篇 ——类的加载原理(上)_第19张图片
输出sels[i] 和sel,发现其方法名字是一样的,那么为什么会进来呢?肯定是他们的地址不同。
iOS 底层探索篇 ——类的加载原理(上)_第20张图片
输出地址证明一下,发现两者地址确实不一样。
iOS 底层探索篇 ——类的加载原理(上)_第21张图片
这里的sels是从mach-o读取出来的,而sel是从dyld读取出来的,dyld 是链接整个程序的,所以以dyld为基准。

接下来看discover classes,这里会在mach-o读取class,然后也在readClass里面调用popFutureNamedClass判断newCls是不是future class,是的话就重新赋值,然后进入下面的if里面进行处理,不是的话就不处理。什么是future class呢?在类的加载中,有时候一些类读取完后就会被删除,但是没有删除干净,就会出现一些混乱,future class 就是没有被删除干净的类

iOS 底层探索篇 ——类的加载原理(上)_第22张图片
readClass 里面还对class进行了一些处理,从machO里面读取出来的是类的地址,而当运行了readClass之后,类的名字就被赋值了。
在这里插入图片描述
iOS 底层探索篇 ——类的加载原理(上)_第23张图片
那么readClass 是怎么处理的呢?点击进去看一下。
这里研究自己创建的类,所以设置一个条件打下断点后运行进来,就是要研究的LGPerson类了。
iOS 底层探索篇 ——类的加载原理(上)_第24张图片

接下来往下运行看看会进去哪里,发现这三个地方是不进去的。
iOS 底层探索篇 ——类的加载原理(上)_第25张图片
iOS 底层探索篇 ——类的加载原理(上)_第26张图片
在这里插入图片描述
往下走发现调用了addNamedClass,为类添加了名字
iOS 底层探索篇 ——类的加载原理(上)_第27张图片
继续往下走,发现会调用addClassTableEntry把类以及他的元类添加到allocatedClasses表中
iOS 底层探索篇 ——类的加载原理(上)_第28张图片
iOS 底层探索篇 ——类的加载原理(上)_第29张图片
在往下走,就直接返回了。这里发现了readClass给类加上了名字,但是ro和rw还没处理。那么在哪里处理的呢?且听下回分解。

你可能感兴趣的:(iOS底层,ios,xcode,swift,objective-c,iphone)