dyld之分类加载

687 阅读5分钟

前言

上一篇博客我们将解了类的加载,这篇文章我们主要探索一下分类是如何加载。

分类的结构

  1. 新建QHPerson+QHA分类,然后运行clang -rewrite-objc QHPerson+QHA.m -o QHPerson+QHA.cpp命令,得到QHPerson+QHA.cpp文件 2.在代码找到了实际上分类也是结构体category_t
struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

其中name:也就是分类的名字,比如QHAcls表示关联的类。还有实例方法类方法协议属性

rwe什么时候初始化

从上一篇博客中知道,加载分类会调用attachCategories方法,但是加载之前必须存在rwe

rwe.pngattachCategories中我们同样找到初始化方法cls->data()->extAllocIfNeeded()。也就是在添加分类前会调用。

延伸

除了添加分类,什么时候也会初始化rwe呢?在源码全局搜索extAllocIfNeeded,发现动态的添加方法协议属性,都会初始化。 #attachLists算法分析

void attachLists(List* **const** * addedLists, uint32_t addedCount) {

        if (addedCount == 0) return;
        if (hasArray()) {

            // many lists -> many lists

            uint32_t oldCount = array()->count;

            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];

            for (unsigned i = 0; i < addedCount; i++)

            newArray->lists[i] = addedLists[i];

            free(array());

            setArray(newArray);

            validate();

        }

        else if (!list  &&  addedCount == 1) {

            // 0 lists -> 1 list

            list = addedLists[0];

            validate();

        } 

        else {

            // 1 list -> many lists

            Ptr<List> oldList = list;

            uint32_t oldCount = oldList ? 1 : 0;

            uint32_t newCount = oldCount + addedCount;

            setArray((array_t *)malloc(array_t::byteSize(newCount)));

            array()->count = newCount;

            if (oldList) array()->lists[addedCount] = oldList;

            for (unsigned i = 0; i < addedCount; i++)
            array()->lists[i] = addedLists[i];
            validate();
        }

    }
  • 这个算法分为三种情况
  1. 第一次如果主类的没有方法,那么分类的方法之间加进去
  2. 如果主类有方法,并且只有一个分类,那没将主类的方法先放到最后,让后在前面插入分类的方法
  3. 如果分类有多个,那么先将之前的方法放入数组,让后在前面插入新的分类方法。 分类方法和主类方法排列如下图:

截屏2021-07-18 下午8.31.46.png

不同情况分析分类的加载情况

下面分不同的情况主类和分类是否有load方法的组合来探索,分类方法是什么时候添加到methods里面

1.主类有load,分类也有load

realizeClassWithoutSwift方法添加断点:

图片一.png 发现ro里面并没有分类方法,继续执行后堆栈会发生变化,会通过load_images->loadAllCategories->load_categories_nolock->attachCategories这样的流程添加分类

2.主类有load,分类没有load

跟踪断点调试:realizeClassWithoutSwift->methodizeClass->attachToClass 并没有调用添加分类的方法

realizeClassWithoutSwift查看ro里面方法的数据:

ro.png

3.主类没有load,分类有load

跟踪断点调试:realizeClassWithoutSwift->methodizeClass->attachToClass 并没有调用添加分类的方法

重复2的操作,发现和2一样,也是在编译阶段就已经加入了分类数据

4.主类没有load,分类也没有load

发现直接进入了main函数,所有的断点都没有走 main函数.png 然后继续执行:

发送消息.png 发现发送消息的时候会调用起realizeClassWithoutSwift方法去实现,打印ro的值,发现也有分类和类的方法。

小结

通过上面的几种情况分析,分类的加载分不同的情况,主类和分类是否实现load方法会影响分类加载时间。总的来说如果我们在分类里面些过多的load方法,会影响dyld的启动程序速度。如无必要我们尽量减少在主类和分类中添加load方法。等到程序启动后懒加载时再去加载。

类扩展

  • 类扩展几乎,在我们开发过程中几乎非常常用.每当我没不想暴露属性,或者方法的时候会在类扩展里面声明。
  • 类扩展可以直接在.m里面添加,也可以单独创建.h文件 为了方便编译到一起,在.m里面添加类扩展,通过clang -rewrite-objc QHPerson.m -o QHPerson.cpp编译

截屏2021-07-22 下午10.07.23.png 发现扩展方法也编译进去了,也就是说类扩展的方法在编译阶段就和类绑定到了一起。

验证

在主类里面实现load方法,运行objc源码:在realizeClassWithoutSwift下断点,

添加断点.png 打印ro里面的方法

打印.png 同样在ro里面也有,这一点证实了我们的结论。

分类关联对象

查看源码

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

object:要关联的对象 key:标识符 value:关联value policy:关联策略

_object_set_associative_reference

  • disguised:将objc对象包装成统一的数据结构
  • association:ObjcAssociation association{policy, value},将value和policy包装成统一的格式,以便存储

关联对象结构存储图

关联对象的存储实际上是一个二级的hashmap结构,如下图: 结构图.png

源码验证

添加关联属性,然后调试

调试1.png

调试二.png 打印disguised 和association

打印.png

AssociationsHashMap

看看AssociationsHashMap &associations(manager.get()); 实际上associations是一个全局的静态变量:

maps.png associations数据接口,有一个Buckets指针

数据结构.png

关联对象存储

存储流程分析:

流程分析.png

  1. 第一步先判断之前的表中是否有要关联的对象
  2. 如果没有先创建或替换。 3.调用try_emplace方法

截屏2021-07-26 上午10.46.31.png

  • 根据key寻找bucket,如果有直接返回,如果没有找到,则插入到hash表中。

关联对象移除,

关联对象什么时候释放呢?实际上是在调用delloc的时候

通过源码_objc_rootDealloc->rootDealloc->object_dispose->objc_destructInstance 如下图:

截屏2021-07-26 上午10.54.34.png 如果有关联对象,会通过_object_remove_assocations