iOS-底层探索14:分类的加载(类的加载下)

iOS 底层探索 文章汇总

目录

  • 一、前言
  • 二、readClass方法调用后类的结构探索
  • 三、将分类中的方法添加到类中
  • 四、懒加载与非懒加载下分类的加载情况
  • 五、LLVM相关流程分析


一、前言

上一篇文章iOS 懒加载类与非懒加载类(类的加载中)中我们分析了懒加载类与非懒加载类的加载流程,并分析了分类的加载原理。我们知道了什么时候添加分类,什么时候初始化rwe,但是什么时候将分类添加到类中不知道。那么这篇文章我们就继续往下探索。

二、readClass方法调用后类的结构探索

iOS-底层探索14:分类的加载(类的加载下)_第1张图片
readClass
  • 可以看到进入readClass方法时cls中并没有bits数据。
iOS-底层探索14:分类的加载(类的加载下)_第2张图片
realizeClassWithoutSwift
  • 进入realizeClassWithoutSwift方法后cls中也没有bits数据。

但是可以发现代码中是可以读取到cls->data();数据,其中data就是从bits中获取的。那为什么lldb中没有读取到bits数据呢?

因为lldb中的x/4gx cls是从内存中读取cls的数据,但此时类的数据空间还没在内存中分配完毕,bits的初始化和赋值还没执行。而代码中却是从macho中通过指针地址读取类的bits数据,类是唯一的,编译器在编译期就将类的指针分配完毕,每次运行类的指针都是相同的。

通过地址指针读取bits数据:

iOS-底层探索14:分类的加载(类的加载下)_第3张图片

setInstancesRequireRawIsaRecursively方法前bits为空

iOS-底层探索14:分类的加载(类的加载下)_第4张图片

setInstancesRequireRawIsaRecursively方法后bit添加了前面的数据
setInstancesRequireRawIsaRecursively方法注释为:将这个类及其所有子类标记为需要原始isa指针

iOS-底层探索14:分类的加载(类的加载下)_第5张图片

setInstanceSize方法前bits还没有后面的数据:

iOS-底层探索14:分类的加载(类的加载下)_第6张图片

setInstanceSize方法后bits有了后面的数据:

iOS-底层探索14:分类的加载(类的加载下)_第7张图片

setHasCxxDtor方法继续往bits中添加数据:

iOS-底层探索14:分类的加载(类的加载下)_第8张图片


三、将分类中的方法添加到类中

前提:将主类及分类的实现文件中均加入+load实现,使之都变为非懒加载类

在上篇文章中我们知道了rwe是在attachCategories方法调用如下代码进行初始化的:

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
...
    auto rwe = cls->data()->extAllocIfNeeded();
...
}

extAllocIfNeeded方法是调用extAlloc

iOS-底层探索14:分类的加载(类的加载下)_第9张图片
extAlloc

iOS-底层探索14:分类的加载(类的加载下)_第10张图片
  • 初始化rwe过程中将主类LGPerson中的所有方法添加进了rwe

attachCategories方法中初始化rwe完成后回到attachCategories方法

iOS-底层探索14:分类的加载(类的加载下)_第11张图片

在这里添加分类中的方法到rwe中,第二个分类重复这个步骤添加方法到rwe中。

iOS-底层探索14:分类的加载(类的加载下)_第12张图片
  • attachLists方法中后添加分类方法列表时会先开辟一块内存,将分类的方法放在新列表的前面,之前的方法列表通过内存平移放到新列表的后面。所以分类主类有同名方法时会调用分类中的方法。

所以分类的加载流程如下:


iOS-底层探索14:分类的加载(类的加载下)_第13张图片


四、懒加载与非懒加载下分类的加载情况

  1. 主类为非懒加载类(+load)、分类一为非懒加载分类(+load)、分类二为懒加载分类
    只要分类中有一个为非懒加载分类那么所有分类均会标记为非懒加载分类
    load_images方法开始获取rwe数据

  2. 主类为懒加载类、分类均为懒加载分类
    第一次消息发送的时候加载类信息,rwe数据从machoclsdata()获取

  3. 主类为非懒加载类(+load)、分类均为懒加载分类
    rwe数据从machoclsdata()获取

  4. 主类为懒加载类、分类均为非懒加载分类(+load)
    分类迫使主类变为非懒加载类样式来提前加载数据

iOS-底层探索14:分类的加载(类的加载下)_第14张图片


五、LLVM相关流程分析

在类的加载过程中可以直接从macho中读取内存地址并能获取class_ro_t格式的数据,那是什么时候通过内存地址生成class_ro_t格式的数据呢?

iOS-底层探索14:分类的加载(类的加载下)_第15张图片
readClass

class_ro_t数据结构:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        ...
    }

    method_list_t *baseMethods() const {
        return baseMethodList;
    }

    class_ro_t *duplicate() const {
        ...
    }
};

由于macho文件是在编译期生成的,所以生成class_ro_t格式数据的时期也在编译期通过LLVM生成的。LLVM源码地址

LLVM下的class_ro_t数据结构:

iOS-底层探索14:分类的加载(类的加载下)_第16张图片

通过Read方法的源码可知class_ro_t中的数据在Read方法中赋值,找到Read方法的调用地址就能知道在哪个方法进行了class_ro_t格式数据的生成。

你可能感兴趣的:(iOS-底层探索14:分类的加载(类的加载下))