什么是类?
类是具有一系列相同行为和属性特征的事物
在iOS中Class是类,一个结构体指针
下面简化后的结构:
strcut objc_class : objc_object {
// Class ISA;
Class superclass; // 指向父类的指针 (由此可见,OC中只能是单继承)
cache_t cache; // 缓存
class_data_bits_t bits; // 类的数据
// 若干方法
……
}
从结构中看到,类其实本质上也是一个对象。
除了ISA外,又新增了:superclass, cache,bits三个成员。
cache 存放类的相关缓存数据(比如实例大小,是否是元类,是否有C++函数等等)
bits 存放类的 class_rw_t (读写) 和 class_ro_t (只读)等等数据
类的结构
类存放在磁盘时候的结构如下:
class_ro_t里的信息是可以从磁盘上再次加载到的。
这个时候,类所占用的内存依然是clean memory,消耗的代价比较小。
类被使用后,在内存中的结构如下:
类加载到内存中后,就会生成class_rw_t的数据。
比如:First Subclass 和 Next Sibling Class 是运行时候写入的。
2020年WWDC的视频 里提到苹果对内存做了一些优化,新增了class_rw_ext_t.
究其原因是将不变的部分保持,变化的部分用扩展结果来存取,因为大部分的类不会做新增方法等操 作,所以这样改变后,内存优化了很多。
这个时候,类占用的内存变成了dirty_memory,相对来说较为昂贵。
从这个意义上来说,尽量避免做一些动态扩展终归是好的。
在启动阶段runtime对类做了什么处理?
启动阶段,runtime注册了三个监听,在dyld加载了image后,会有map_images,
load_images,unmap_images三个回调。
map_images的回调中
-
map_images的时候,会先从可执行文件中拿到类的集合列表 -
然后开始调用
read_images,进而触发readClass来读取类,将class塞入到gdb_objc_realized_classes这个mapTable中 -
同时将类及其元类放到
allocatedClasses的表里 -
从
MACH-O中可以拿到非懒加载的类集合(比如实现了+(void)load方法的类),进行加载。注意类的load方法会导致类提前被加载
load_images的回调中
-
load_images的时候,先从可执行文件中拿到所有的分类,调用unattachedCategories.addToClass将分类放到一个集合中。 -
将含有
+(void)load的类,放到集合中 -
将含有
+(void)load方法的分类从可执行文件中拿出来,先将分类所属的Class进行加载,然后将这些分类放到集合中 (注意分类的load方法会导致类提前被加载) -
遍历含有load方法的类集合,执行load方法
-
遍历含有load方法的分类集合,执行load方法
简易流程图如下
graph TD
map_images --> 将类从可执行文件中读取出来 --> read_images --> 将类及其元类放到对应的集合中 --> 对于实现了load方法的类进行加载
load_images --> 将分类信息从可执行文件中读取出来 --> 将分类信息放到对应的集合中 --> 将实现了load方法的类从可执行文件中读取放入到集合中 --> 将实现了load方法的分类从可执行文件中读取出来放入到集合中 --> 将分类所属的类进行加载 --> 按顺序遍历两个集合并调用load方法
类的加载过程
-
生成
rw信息 -
递归调用,将父类以及元类先进行加载
-
绑定父类和元类等信息
-
给类添加方法列表,属性,协议等信息
-
调用
objc::unattachedCategories.attachToClass,将分类的相关信息绑定到类里去
简化流程图如下:
graph TD
生成rw信息 --> 将父类以及元类进行加载 --> 绑定父类和元类等信息 --> 给类添加方法列表属性/协议等信息 --> 将分类的相关信息绑定到类里去
总结
在iOS中,类是一个结构体指针,类的结构体中包含了元类,父类的指针以及方法列表,属性列表等等信息。
大部分的类都是通过懒加载的方式进行处理(即在第一次使用的时候去加载),但是实现了+(void)load方法的类,会在启动阶段提前被加载。
类加载的过程中,还需要先加载其父类,元类,分类等信息。
因此建议,尽量避免+(void)load,毕竟load方法中如果涉及到其他类的调用,也会导致其他类的加载提前。