类和分类的加载

260 阅读6分钟

前言

通过dyld和objc关联我们已经知道,非懒加载的类会在函数realizeClassWithoutSwift进行数据的加载,今天我们就来探究一下类的方法、属性和协议等数据是如何加载到rorwrwe中的。

首先来介绍一下什么是rorwrwe

  • 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.我们将LGPersonload方法去掉使其成为懒加载的类,在main函数中调用LGPersonalloc方法初始化一个类对象;

这里的断点保证当前研究对象是LGPerson

2.在realizeClassWithoutSwift函数中断点调试

3.在控制台bt打印堆栈信息

通过堆栈信息打印我们可以发现,当调用LGPersonalloc方法时进入消息转发,来到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,并设置clsrw.

确实isa指向

  • 确定继承关系;
  • 设置C++的析构函数.

确定子类和父类的继承与被继承的双向链表关系

递归调用,确认继承链

通过realizeClassWithoutSwift函数,类的信息从data中加载到了ro中,将从ro copy到了一份到rw中,类的加载完成。

分类的加载

1.在main.m文件中添加LGPerson的分类

2.使用 clang -rewrite-objc main.m -o main.cppmain.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_addPropertyclass_addProtocoladdMethodsclass_setVersiondemangledNameattachCategories中都有调用extAllocIfNeeded函数.

attachLists函数分析

  • 当没有分类时,走else if方法,直接赋值,结果是一维数组;
  • 当只有一个分类时,走else方法,开辟新的内存空间,将类的数据copy到数组的最后面,将新添加进来的数据放在前面;
  • 当有多个分类时,走if,开辟新的内存空间,将之前的数据移动到数组的最后面,将新添加进来的数据放在前面;

##分类的加载时机

类和分类都实现了load方法

此时类和分类都是非懒加载的,会有dyly关联objc阶段加载分类数据。

只有类实现了load方法

类是非懒加载的,会在编绎时期将分类的数据加载到macho文件中,在类加载的时候从data中加载到类中。

只有分类实现了load方法

此时,类是懒加载的,分类是非懒加载的,在dyly关联objc阶段会加载分类数据,但是分类数据需要保存到类中,所以此时会迫使类以非懒加载的形式加载。

类和分类都没有实现load方法

类和分类都是懒加载的,会在第一次调用方法的消息发送时加载类数据和分类数据。

分类的加载总结

总结

  • 类的加载分为非懒加载类和懒加载类的加类,非懒加载类的加类在dyld关联objc阶段完成;懒加载类的加载在第一次使用到该类,即第一次消息发送时完成加载。
  • 分类的本质是结构体,分类的加载同样分为非懒加载分类和懒加载分类;
  • 分类的数据最终会加载到类的rwe中的,这样我们就可以通过类或类的实例调用分类中的方法了。