ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-07-14 10:04:10  阅读:199  来源: 互联网

标签:OBJC map iOS objc init images dyld 加载 底层


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

1. objc_init 做了什么

上文说到了objc_init调用了_dyld_objc_notify_register,初始化了dyld 里面的sNotifyObjCMapped,sNotifyObjCInit,sNotifyObjCUnmapped函数并对 sNotifyObjCMapped,sNotifyObjCInit直接进行了调用,那么objc_init还做了什么呢?
在这里插入图片描述
首先看到environ_init,这里面主要做了环境变量的初始化。那么环境变量有什么用呢?先把环境变量打印出来。
在这里插入图片描述
然后运行,发现有很多的环境变量,其中有OBJC_DISABLE_NONPOINTER_ISA和OBJC_PRINT_LOAD_METHODS。
在这里插入图片描述
在这里插入图片描述

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 可以得到类的信息。
在这里插入图片描述
OBJC_PRINT_LOAD_METHODS就是会把调用load方法的地方打印出来,添加OBJC_PRINT_LOAD_METHODS环境变量并且设为YES。
在这里插入图片描述
运行一下。
在这里插入图片描述
环境变量也可以通过中在终端中输入export OBJC_HELP=1来查看。在终端中输入export OBJC_HELP=1 后在输入ls,可以看到环境变量在终端中被打印出来了。
在这里插入图片描述
environ_init看完,下一个就是tls_init, tls_initz做的就是设置objc的预定义的线程特定键和键的析构函数,来存储objc的私有数据。
在这里插入图片描述
再往下就是static_init,static_init的作用就是调用全局静态c++函数(objc库里面的)。因为这里已经开启了runtime的下层,这里自己调用c++函数是为了方便所有的环境能够准备充分。
在这里插入图片描述
接下来就是runtime_init。这里进行了两个表的初始化。
在这里插入图片描述
在往下就是exception_init,这里做的是初始化libobjc的异常处理系统。
在这里插入图片描述
往下是cache_init,这里做的是缓存条件初始化。继续往下就是_imp_implementationWithBlock_init,这里启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。
在往下就是重点: _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了。

看一下map_images的实现。
在这里插入图片描述

在点击map_images_nolock查看。这里重要的是寻找image的读取和映射,这里找到了_read_images这个方法。
在这里插入图片描述
点击_read_images,然后看到代码行数太多。接下来就从整体来看这个方法,把代码块收起来。可以看到这里有log记录每个方法做了些什么。
在这里插入图片描述
read_images流程:

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

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

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

在这里插入图片描述
readClass 里面还对class进行了一些处理,从machO里面读取出来的是类的地址,而当运行了readClass之后,类的名字就被赋值了。
在这里插入图片描述
在这里插入图片描述
那么readClass 是怎么处理的呢?点击进去看一下。
这里研究自己创建的类,所以设置一个条件打下断点后运行进来,就是要研究的LGPerson类了。
在这里插入图片描述

接下来往下运行看看会进去哪里,发现这三个地方是不进去的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
往下走发现调用了addNamedClass,为类添加了名字
在这里插入图片描述
继续往下走,发现会调用addClassTableEntry把类以及他的元类添加到allocatedClasses表中
在这里插入图片描述
在这里插入图片描述
在往下走,就直接返回了。

标签:OBJC,map,iOS,objc,init,images,dyld,加载,底层
来源: https://blog.csdn.net/LinShunIos/article/details/118694999

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有