【iOS】类扩展与关联对象

发布于:2025-07-30 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

前言

类扩展

类扩展的本质

关联对象

设值流程

_object_set_associative_reference 方法

取值流程

objc_getAssociatedObject

关于类扩展和分类


前言

之前已经分析了类和分类的加载,那么这篇文章来讲解一下类扩展和关联对象的底层原理

类扩展

类扩展又称为匿名的分类,可以给当前类添加属性方法

类扩展的本质

写一个类扩展,生成cpp文件,并在文件中查找扩展中添加的属性

可以看到cpp文件中存在带下划线的该成员变量,并且合成了set和get方法,所以说扩展中可以给类添加属性。

再看看扩展中添加的方法:

可以看到扩展中方法已经添加在方法列表中了,也就是说扩展中添加的方法在编译过程中,就已经添加到了methodlist中,作为类的一部分,即编译时期直接添加到本类里面

关联对象

关联对象底层原理的实现,主要就是两个部分:

  • 通过objc_setAssociatedObject设值流程

  • 通过objc_getAssociatedObject取值流程

设值流程

在分类中重写属性cate_name的set、get方法,通过runtime的属性关联方法实现

这里objc_setAssociatedObject方法有四个参数,分别表示:

  • 参数1:要关联的对象,即给谁添加关联属性

  • 参数2:标识符,方便下次查找

  • 参数3:value

  • 参数4:属性的策略,即nonatomic、atomic、assign

这里objc_setAssociatedObject源码实现中调用_object_set_associative_reference 方法。

_object_set_associative_reference 方法

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;
​
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    //object封装成一个数组结构类型,类型为DisguisedPtr
    //相当于包装了一下 对象object,便于使用
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value}; // 包装一下 policy - value
​
    // retain the new value (if any) outside the lock.
    association.acquireValue();//根据策略类型进行处理
​
    bool isFirstAssociation = false;
    {
        //初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
        //并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一
​
        AssociationsManager manager2;
        AssociationsHashMap &associations2(manager2.get());
        
        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对
            if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
                /* it's the first association we make */
                isFirstAssociation = true;//标记true
            }
​
            /* establish or replace the association */
            //得到一个空的桶子,找到引用对象类型,即第一个元素的second值
            auto &refs = refs_result.first->second;
            //查找当前的key是否有association关联对象
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {//如果结果不存在
                association.swap(result.first->second);
            }
        } else { //如果传的是空值,则移除关联,相当于移除
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
​
                    }
                }
            }
        }
    }
​
    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        object->setHasAssociatedObjects();
​
    // release the old value (outside of the lock).
    association.releaseHeldValue();//释放
}

通过_object_set_associative_reference 源码可知方法主要分为以下几步:

  1. 创建一个 AssociationsManager 管理类

  2. 获取唯一的全局静态哈希Map:AssociationsHashMap

  3. 判断 value 是否存在(value为空则走清除逻辑)

  4. 尝试插入对象桶(ObjectAssociationMap):以 disguised(object)(通过类得到的,不同的类名该函数返回值不同) 为 key,尝试插入一个空的 ObjectAssociationMap(如果没有的话)。

  5. 插入桶成功(第二层 map 空)时,插入一个空桶(桶是 key→ObjcAssociation)(key 是用户传进来的 key(void*),value 是封装了 policy + valueObjcAssociation

  6. 第一次插入对象时,通过 setHasAssociatedObjects 设置标记位(如果是这个 object 第一次有关联对象,就设置 isa 标志位的 has_assoc = 1

  7. 用 ObjcAssociation 替换旧值(如果之前已有 key)(如果第二层已经有这个 key,不是第一次插入,就调用 swap

  8. 标记第一次插入 ObjectAssociationMap 的布尔值为 true

这里AssociationsManager类型的变量,会自动调用AssociationsManager的析构函数进行初始化,这个源码中加锁是为了避免多线程重复创建,但并不代表唯一

AssociationsHashMap类型的哈希map,这个是唯一的:

可以看到源码是通过_mapStorage.get()生成哈希map,而_mapStorage是一个静态变量,所以哈希map永远是通过静态变量获取的,所以全场唯一

如果传入的value是空值,那么走else流程,else分支中移除关联

try_emplace方法

try_emplace方法负责完成对全局哈希map中插入新的类以及在类对应的表中插入新的键值对。

这里的桶子负责保存键值之间的映射关系,可以理解为保存值,而这里保存的值的类型取决于方法的调用者:

这里第一次调用try_emplace方法时,是在对全局的哈希表和类操作,可以理解为桶子里存的是类,第二次调用try_emplace方法时,是在对类对应的哈希表和关联对象操作。可以理解为桶子里存的是关联对象。

可以得到大致结构是这样的:

AssociationsManager可以有多个,通过AssociationsManagerLock锁可以得到一个AssociationsHashMap类型的map

map中有很多的关联对象map,类型是ObjectAssociationMap,其中keyDisguisedPtr<objc_object>,例如TCJPerson会对应一个ObjectAssociationMapTCJStudent也会对应一个ObjectAssociationMap.

ObjectAssociationMap哈希表中有很多key-value键值对,其中key的类型为const void *,其实这个key从底层这个方法_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)的参数就可以看出,key是我们关联属性时设置的字符串value的类型为ObjcAssociation。其中ObjcAssociation是用于包装policyvalue的一个类。

以上流程总结如下图:

取值流程

objc_setAssociatedObject类似,关联对象get方法的核心是objc_getAssociatedObject

objc_getAssociatedObject

通过源码可知,主要分为以下几部分

  • 1:创建一个 AssociationsManager 管理类

  • 2:获取唯一的全局静态哈希MapAssociationsHashMap

  • 3:通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器

  • 4:如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (policy和value)

  • 5:通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value

  • 6:返回 value

关于类扩展和分类

category 类别、分类

  • 专门用来给类添加新的方法

  • 不能给类添加成员属性,添加了成员属性,也无法取到

    • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写settergetter方法

  • 分类中用@property 定义变量,只会生成变量的settergetter方法的声明不能生成方法实现和带下划线的成员变量

extension 类扩展

  • 可以说成是特殊的分类,也可称作匿名分类

  • 可以给类添加成员属性,但是是私有变量

  • 可以给类添加方法,也是私有方法


网站公告

今日签到

点亮在社区的每一天
去签到