前言
通过dyld和objc关联我们已经知道,非懒加载的类会在函数realizeClassWithoutSwift进行数据的加载,今天我们就来探究一下类的方法、属性和协议等数据是如何加载到ro、rw和rwe中的。
首先来介绍一下什么是ro、rw和rwe
ro是类的结构中class_ro_t类型的一个数据,用来存储编绎时就已经确定的类的方法、属性和协议等数据,它是read-only的干净内存;rw是类的结构中class_rw_t类型的一个数据,在程序运行过程中,需要使用到ro中数据时,为了保护ro中的数据不会在运行时被修改,会把ro中的数据copy一份到rw中,rw可能在运行时被修改,在程序运行过程中我们在读取类信息时读取的就是rw中的数据;rwe是满足OC运行时的特性,将运行时动态生成的方法、属性和协议等数据写入到rwe而设计的,比如分类中实现的方法会在运行时动态的添加到类的数据结构的rw中,这样才能保证可以直接通过类的实例或类对象调用到分类中的方法。
准备工作
首先准备objc4-781源码并配置成可编绎执行
由于系统的类我们无法控制,为了更好的体现类的加载的原理,我们创建一个自己的类LGPerson,实现它的load方法,使其成为一个非懒加载的类。
类的加载分析
非懒加载类的加载
添加相应的代码,保证我们研究的对象是LGPerson
运行程序,断点调试到_read_images函数的非懒加载类处理的地方
由此我们可以确定当前研究的对象确实是实现了
load方法的非懒加载类--LGPerson
懒加载的类的加载
我们已经知道了非懒加载的类是通过_read_images中调用realizeClassWithoutSwift函数进行加载,那么懒加类的又是如何加载的呢?
1.我们将LGPerson的load方法去掉使其成为懒加载的类,在main函数中调用LGPerson的alloc方法初始化一个类对象;
这里的断点保证当前研究对象是
LGPerson
2.在realizeClassWithoutSwift函数中断点调试
3.在控制台bt打印堆栈信息
通过堆栈信息打印我们可以发现,当调用
LGPerson的alloc方法时进入消息转发,来到lookUpImpOrForward函数,最后由lookUpImpOrForward函数来到了realizeClassWithoutSwift函数加载类信息
查看lookUpImpOrForward函数的源码
initializeAndLeaveLocked不就是堆栈信息中lookUpImpOrForward函数下一个执行的函数嘛,!cls->isInitialized()说明当类还没有初始化的时候才会来到这里。由于说明,对于懒加载的类,只有当第一次使用它,才会加载它的类信息。我们的项目中有成千上万的类,而dyld关联objc加载类信息的过程会产生大量的临时变量,并且执行大量的代码,执行排序等操作,如果在dyld阶段加载所有的类信息,无疑是非常消耗性能和时间的。通过懒加载的方式,在使用到时类时才加载类信息,可以大大提高程序的性能和加快程序的启动。
懒加载类和非懒加载类的加载区别
懒加载类和非懒加载类区别是否实现了load方法
realizeClassWithoutSwift函数分析
if (cls->isRealized()) return cls已经实现的类直接return;auto ro = (const class_ro_t *)cls->data()将data数据保存到临时变量ro;- 初始化
rw,将rocopy一份到rw,并设置cls的rw.
确实
isa指向
- 确定继承关系;
- 设置C++的析构函数.
确定子类和父类的继承与被继承的双向链表关系
递归调用,确认继承链
通过realizeClassWithoutSwift函数,类的信息从data中加载到了ro中,将从ro copy到了一份到rw中,类的加载完成。
分类的加载
1.在main.m文件中添加LGPerson的分类
2.使用 clang -rewrite-objc main.m -o main.cpp 将main.m文件编绎成main.cpp文件,有关clang的内容请看OC对象本质及isa结构分析
分类的本质
打开main.cpp文件找到LGPerson(LG)分类
可以看到分类的本质是
_category_t类型的结构体
搜索_category_t,找到它的定义
- name:分类名;
- cls:类;
- instance_methods:实例方法列表;
- class_methods:类方法列表;
- protocols:协议列表;
- properties:属性列表.
可以看到不管是类方法还是实例方法,在分类中都有对应的方法名(sel),方法签名和函数指针(imp)
属性只有声明,没有实现set和get方法.
methodizeClass 函数分析
此函数用于将分类中的方法、协议和属性等数据添加到对应的类中
定位到要研究的类--
LGPerson
将分类中的方法、属性和协议加载到
rwe中?方法先调用prepareMethodLists函数进行排序. 打印一下,rwe,发现是它是NULL,说明此时分类中的数据并没有添加到类中.
下面看attachToClass函数实现
attachToClass函数分析
attachToClass函数调用了
attachCategories函数
attachCategories函数分析
初始化
rwe
将分类中的方法、协议和属性等数据准备好
调用
attachLists函数进行下一步操作
attachCategories函数分析图
在objc4-781源码中全局搜索一下rwe的初始化函数extAllocIfNeeded,我们发现在objc_duplicateClass、_class_addProperty、class_addProtocol、addMethods、class_setVersion、demangledName、attachCategories中都有调用extAllocIfNeeded函数.
attachLists函数分析
- 当没有分类时,走
else if方法,直接赋值,结果是一维数组;- 当只有一个分类时,走
else方法,开辟新的内存空间,将类的数据copy到数组的最后面,将新添加进来的数据放在前面;- 当有多个分类时,走
if,开辟新的内存空间,将之前的数据移动到数组的最后面,将新添加进来的数据放在前面;
##分类的加载时机
类和分类都实现了load方法
此时类和分类都是非懒加载的,会有dyly关联objc阶段加载分类数据。
只有类实现了load方法
类是非懒加载的,会在编绎时期将分类的数据加载到macho文件中,在类加载的时候从data中加载到类中。
只有分类实现了load方法
此时,类是懒加载的,分类是非懒加载的,在dyly关联objc阶段会加载分类数据,但是分类数据需要保存到类中,所以此时会迫使类以非懒加载的形式加载。
类和分类都没有实现load方法
类和分类都是懒加载的,会在第一次调用方法的消息发送时加载类数据和分类数据。
分类的加载总结
总结
- 类的加载分为非懒加载类和懒加载类的加类,非懒加载类的加类在dyld关联objc阶段完成;懒加载类的加载在第一次使用到该类,即第一次消息发送时完成加载。
- 分类的本质是结构体,分类的加载同样分为非懒加载分类和懒加载分类;
- 分类的数据最终会加载到类的
rwe中的,这样我们就可以通过类或类的实例调用分类中的方法了。