美文网首页
iOS底层之类的加载

iOS底层之类的加载

作者: 大橘猪猪侠 | 来源:发表于2020-10-15 22:24 被阅读0次

在iOS底层中,关于类的加载,在应用程序开始加载时,首先通过dyld链接到动态库objc,从objc中的init方法将类中的属性、方法、协议通过编译成Mach-o格式,而在Mach-o中是通过里面的data()读取其中的ro (干净的内存,由于iOS是运行时,需要在内存中不断插入,修改,因此将干净内存copy到rw中,rwe是脏内存,在wwdc2020有详细介绍)rw,那么他是如何加载的,这是本文探索的重点。

而在之前探索过dyld与objc的关联,在objc的源码中我们研究过一个很重要的函数_read_images,在这个函数中,有很多事件的处理:

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

关于类的加载,我们只需要查看第4点和第10点,首先我们以能够运行的objc4-781源码当作工程来研究。

KCObjc中有一个Person类,里面的代码:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
//@property (nonatomic, copy) NSString *kc_name;
//@property (nonatomic, assign) int kc_age;

- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;

+ (void)kc_sayClassMethod;

@end



#import "Person.h"
@implementation Person
//+ (void)load{
//    
//}
- (void)kc_instanceMethod2{
    NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod3{
    NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
    NSLog(@"%s",__func__);
}
@end

可以看到在Person.m中,将load方法进行了注释掉,接下来来探究一下
看下图:

iShot2020-10-15 19.02.34.png

看上图可以得出,当系统对类进行加载时,并不会执行3724-3749之间的代码,而当我们将load方法写出来时,程序执行了3724-3749的代码,如下图:

iShot2020-10-15 19.04.53.png

那么进来之后,我们继续执行下一步:


iShot2020-10-15 19.16.06.png

当我们在3747行打上断点后,发现if (cls->isSwiftStable()) {这个判断中的代码并不会执行,而是直接执行3747行代码,当我们往后继续看代码时(如下图),你会发现,它有多个方法中都有realizeClasses的函数名,那根据之前对方法的慢速查找时,也有着类似的地方。

iShot2020-10-15 19.18.01.png

那么接下来我们来看一下realizeClassWithoutSwift方法的实现,继续执行代码:

iShot2020-10-15 19.24.02.png

在上图中,将对clsdata()值赋值给ro,在接下来中(看下图),会执行方框中的代码,将ro赋值给rw,设置cls的数据为rw

iShot2020-10-15 19.29.19.png

当继续执行程序,突然发现了一个很奇怪的东西,看下图:


iShot2020-10-15 19.34.11.png

当上面的clsrw已经赋值了,在我们打印cls的值时,却始终为空;

那么继续执行程序:


iShot2020-10-15 19.42.52.png

就会走到下面的递归程序中,对父类,元类进行递归执行;

iShot2020-10-15 19.45.14.png

在上图中,当程序执行到此处时,我们发现在进这个方法时,cls是Person,而在此处编程了地址,这是因为这是元类信息,而这个地址跟我们之前打印的cls的isa地址一摸一样。

而执行到这个方法最后,有一个很重要的方法:! iShot2020-10-15 20.01.00.png

下面我们继续进入这个方法去看一下他的源码实现:


iShot2020-10-15 20.17.26.png

方框中是自己写判断探索的类为自己创建的类,因为系统的类并不好判断,只能使用我们自己的类去探索,我们现在去看一下class_ro_t是什么东西,先看一下他的结构:

iShot2020-10-15 20.19.39.png

上图中,方框中的属性是方法列表。

以下的代码是我利用控制台对kc_ro的一些输出,可以看到

(lldb) p kc_ro
(const class_ro_t *) $0 = 0x00000001000080a8
(lldb) p $0.baseMethodList
(method_list_t *const) $1 = 0x00000001000080f0
  Fix-it applied, fixed expression was: 
    $0->baseMethodList
(lldb) p *$1
(method_list_t) $2 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 3
    first = {
      name = "kc_instanceMethod2"
      types = 0x0000000100003f9e "v16@0:8"
      imp = 0x0000000100003de0 (KCObjc`-[Person kc_instanceMethod2])
    }
  }
}
(lldb) p $2.get(0)
(method_t) $3 = {
  name = "kc_instanceMethod2"
  types = 0x0000000100003f9e "v16@0:8"
  imp = 0x0000000100003de0 (KCObjc`-[Person kc_instanceMethod2])
}
(lldb) p $2.get(1)
(method_t) $4 = {
  name = "kc_instanceMethod1"
  types = 0x0000000100003f9e "v16@0:8"
  imp = 0x0000000100003e10 (KCObjc`-[Person kc_instanceMethod1])
}
(lldb) p $2.get(2)
(method_t) $5 = {
  name = "kc_instanceMethod3"
  types = 0x0000000100003f9e "v16@0:8"
  imp = 0x0000000100003e40 (KCObjc`-[Person kc_instanceMethod3])
}
iShot2020-10-15 20.20.46.png

而继续执行程序,下面的代码会很有意思,在prepareMethodLists方法中有一个fixupMethodList,在这个方法中,会对方法进行排序,如下图:

iShot2020-10-15 20.22.31.png

下面去验证一下它是否排序:

(lldb) p mlist
(method_list_t *) $6 = 0x00000001000080f0
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 3
    first = {
      name = "kc_instanceMethod1"
      types = 0x0000000100003f9e "v16@0:8"
      imp = 0x0000000100003e10 (KCObjc`-[Person kc_instanceMethod1])
    }
  }
}
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "kc_instanceMethod1"
  types = 0x0000000100003f9e "v16@0:8"
  imp = 0x0000000100003e10 (KCObjc`-[Person kc_instanceMethod1])
}
(lldb) p $7.get(1)
(method_t) $10 = {
  name = "kc_instanceMethod2"
  types = 0x0000000100003f9e "v16@0:8"
  imp = 0x0000000100003de0 (KCObjc`-[Person kc_instanceMethod2])
}
(lldb) p $7.get(2)
(method_t) $9 = {
  name = "kc_instanceMethod3"
  types = 0x0000000100003f9e "v16@0:8"
  imp = 0x0000000100003e40 (KCObjc`-[Person kc_instanceMethod3])
}

可以看到方法列表名是排序的;

继续执行程序,看下图所示:


iShot2020-10-15 20.26.47.png

当排序完之后,这个rwe值为空,也就是说,我们探究到了data()->ro->rw->rwe(这一步没执行,值为空)

而在上面,探究过在类中加入了load方法后,就执行了非那一段非懒加载的代码,那么非懒加载类和懒加载类是什么意思呢?
就是说方法在需要的时候就加载,不需要的时候就不加载;

那么结果就是,当有load方法时,他就是非懒加载类,没有load时,就是懒加载类,当有需要让程序提前执行时,就添加load方法,

而非懒加载类为什么会来,是由于消息转发,请看下图,当没有load方法,执行到realizeClassWithoutSwift方法之后,我们去打印堆栈信息,从dyld_start->_objc_msgSend_uncached->lookUpImpOrForward->realizeClassWithoutSwift,可以得到,这一过程是消息转发的过程:

iShot2020-10-15 20.36.45.png

而为什么要这样呢,因为在加载方法的过程中,会有很多代码,对方法进行排序,如果方在main函数之前,那么main的启动会非常慢,那么当你的方法从开始到结束都没调用,那这样运行会耗费内存,而设计懒加载,节省了内存的消耗。

所以在我们一般开发中,不建议用load

下面对懒加载类和非懒加载类进行一些总结:

1: 懒加载类情况 数据加载推迟到第一次消息的时候

lookUpImpOrForward
realizeClassMaybeSwiftMaybeRelock
realizeClassWithoutSwift
methodizeClass

2: 非懒加载类情况 map_images的时候 加载所有类数据

_getObjc2NonlazyClassList
readClass
realizeClassWithoutSwift
methodizeClass

在上面的探究过程中,漏了对分类的探究,下面来简单的来探究一下分类的加载,在下一篇文章中将会详细介绍分类的加载。

在main.m中添加分类代码:

@interface Person (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;

@end

@implementation Person (LG)

- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}

- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}

+ (void)cate_sayClassMethod{
    NSLog(@"%s",__func__);
}
@end

在终端通过指令clang -rewrite-objc main.m -o main.cpp编译cpp文件。

看下图,当编译完cpp文件后,找分类的结构,可以看到一个_category_t的结构,接下来去看一下这个结构体的结构:

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};
iShot2020-10-15 21.32.23.png

方法代码:函数名就是imp,是一个method_t结构,而分类中的属性没有set,get方法,只能通过关联属性实现set,get方法。

iShot2020-10-15 21.36.39.png

那么继续回到objc源码中来,从上面的探索中,分类是在methodizeClass方法中,而在上面我们通过对类方法进行排序的验证之后,继续执行代码,如下图所示:

iShot2020-10-15 21.44.45.png

在上图中,程序中有分类,但是这个分类的代码并没有执行,直接跳到1480行代码,unattachedCategories的初始化在runtime,那么继续去看看attachToClass方法的实现:

iShot2020-10-15 21.52.08.png

在上图中,我们将程序继续执行,却发现程序直接跳过了1150-1161行,而在这中间有一个重要的方法attachCategories,这里有两个一样的函数调用,一个是实例方法,一个是类方法,那么我们就进入这个方法看一下:

iShot2020-10-15 21.54.51.png

上图是一部分代码,其中图片中是比较重要的一部分:

下图是对一些数据的处理,其中cats_count值为1是因为在前面是一个方法一个方法的进来,而ATTACH_BUFSIZ的值为64,则1374行的作用就是倒叙插入。

iShot2020-10-15 21.56.46.png

来看一下mlist的结构,


iShot2020-10-15 22.01.29.png

继续执行,当执行完循环后,看下图:
执行完循环,在1398处打断点,当程序执行到1400处后,rwe有值了;


iShot2020-10-15 22.08.41.png

rwe是什么时候开始有值了呢?
我们往上看代码,如下图:

iShot2020-10-15 22.11.13.png

可以看到rew在此处调用了一个方法;
这个方法对rew进行判断,如果存在,就直接获取,如果不存在,就开辟创建,如下两图:


iShot2020-10-15 22.11.59.png iShot2020-10-15 22.13.40.png

rwe是在分类的添加的时候才进行处理,所以在这个时候才会进行初始化。

在经过一翻查询后,可以得到分类的添加处理在addMethodaddProaddprotocol的时候都会进行处理。

相关文章

  • iOS底层之类的加载

    在iOS底层中,关于类的加载,在应用程序开始加载时,首先通过dyld链接到动态库objc,从objc中的init方...

  • iOS底层原理之类的加载处理

    1. _objc_init 程序在启动时,先用dyld进行动态库的链接,做完一系列准备操作之后,会进入到_objc...

  • JVM底层之类加载

    JVM包含四个部分:类加载子系统、运行时数据区、执行引擎和垃圾回收器。 普通的Java类在JVM中对应的是inst...

  • iOS底层探索之类的加载(三): attachCategorie

    1.回顾 在上篇博客中,已经对类的加载有了一定的了解,分类的加载也定位到了attachCategories方法中,...

  • iOS底层探索之类的加载(二): realizeClassWit

    1. 回顾 在上篇博文中,已经从dyld到_objc_init再到read_images整个流程串联起来了,最后定...

  • 15.iOS底层学习之类的加载分析

    上一篇文章学习了read_images相关的流程,发现类的初始和方法realizeClassWithoutSwif...

  • 越狱简介

    正常启动iOS系统的流程 引导ROM【只读】->LLB【加载底层引导加载器】->iBoot【加载操作系统内核】->...

  • iOS底层之类结构分析

    上篇文章: iOS底层之isa走位探索 前言 从上篇文章中我们了解了对象的isa指针的走位逻辑,接下来咱们分析一下...

  • iOS底层探索之类结构

    一、前置知识 CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被...

  • iOS底层之类的结构分析

    从iOS底层之isa结构分析及关联类我们探究了类的实例对象的内存结构,对象指针的首地址存储了isa,也就是存储了类...

网友评论

      本文标题:iOS底层之类的加载

      本文链接:https://www.haomeiwen.com/subject/crhapktx.html