iOS 类的加载简单探究

240 阅读3分钟

什么是类?

类是具有一系列相同行为和属性特征的事物

iOSClass是类,一个结构体指针

下面简化后的结构:


   strcut objc_class : objc_object {
        //  Class ISA; 
        Class superclass; // 指向父类的指针 (由此可见,OC中只能是单继承)
        cache_t cache; // 缓存
        class_data_bits_t bits; // 类的数据

        // 若干方法
        ……
    }

从结构中看到,类其实本质上也是一个对象。

除了ISA外,又新增了:superclasscachebits三个成员。

cache 存放类的相关缓存数据(比如实例大小,是否是元类,是否有C++函数等等)

bits 存放类的 class_rw_t (读写) 和 class_ro_t (只读)等等数据

类的结构

类存放在磁盘时候的结构如下:

image.png

class_ro_t里的信息是可以从磁盘上再次加载到的。

这个时候,类所占用的内存依然是clean memory,消耗的代价比较小。

类被使用后,在内存中的结构如下:

image.png

类加载到内存中后,就会生成class_rw_t的数据。

比如:First SubclassNext Sibling Class 是运行时候写入的。

2020年WWDC的视频 里提到苹果对内存做了一些优化,新增了class_rw_ext_t.

究其原因是将不变的部分保持,变化的部分用扩展结果来存取,因为大部分的类不会做新增方法等操 作,所以这样改变后,内存优化了很多。

image.png

这个时候,类占用的内存变成了dirty_memory,相对来说较为昂贵。

从这个意义上来说,尽量避免做一些动态扩展终归是好的。

在启动阶段runtime对类做了什么处理?

启动阶段,runtime注册了三个监听,在dyld加载了image后,会有map_images

load_imagesunmap_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方法中如果涉及到其他类的调用,也会导致其他类的加载提前。