美文网首页
OC底层探索(十八): 类扩展和关联对象

OC底层探索(十八): 类扩展和关联对象

作者: ShawnAlex | 来源:发表于2022-05-27 23:59 被阅读0次

    所用版本:

    • 处理器: Intel Core i9
    • MacOS 12.3.1
    • Xcode 13.3.1
    • objc4-838

    先看下分类和类扩展区别

    分类category
    • 专门给类添加新的方法
    • 不能给成员属性添加成员变量也无法获取到(可以通过runtime给分类添加属性)
    • 分类中定义@property, 只会生产set, get 方法, 不会生成员变量
    扩展extension
    • 可以说特殊分类, 也称作匿名分类
    • 可以给类添加成员变量(私有变量)
    • 可以给类添加方法 (私有方法)

    其实我们常用的@interface XXX()就是类扩展, 没想到吧, 用的频率比分类都多 :)。

    同时留意下:

    • @interface XXX()类扩展位置写在 声明之后, 实现之前, 不然会报错。
    • @interface XXX()里面属性不能写进+load之中, 不然会报错。
    错误写法1 错误写法2

    类扩展extension

    现在在类扩展中加个属性, 方法

    例子

    Clang一下看下底层内部实现

    // 命令
    clang -rewrite-objc main.m -o main.cpp
    
    cpp

    可看到, 扩展 方法和属性的set, get 方法就直接添加到 method_list中,作为类的一部分。其实类扩展是在编译时直接添加到本类里面, 与分类不同, 分类会生成一个category_t 结构体会影响主类, 但是类扩展不会。我们可以在realizeClassWithoutSwift中读一下ro验证下:

    调用

    留意下: 要先有个调用, 不然不会加载. 挺好的, 省着浪费内存空间

    验证

    可发现类扩展方法已经在ro中, 从而也可验证类扩展直接添加到本类中



    当然类扩展也可以单独创建, 看下extension创建

    extension 创建

    Objctive-C FileFile Type 选择Extension即可

    实现

    但是只会生成一个.h 不会生成.m, 实现还是要再本类.m中进行

    总结:

    • 类扩展不会影响类的编译和加载。(会作为类的一部分,和类一起编译, 但是分类会影响 !!!)
    • 类扩展可以写多个,最后也都是合并进主类了
    • 类扩展必主类搭配使用,实现要在本类中实现

    关联对象

    错误例子 错误例子

    之前写分类也说过, 分类是不能添加属性的(只声明 get & setter无法存取), 但是可以通过关联对象方法添加属性。关键方法:

    • objc_setAssociatedObject
    • objc_getAssociatedObject

    先看下效果:


    例子 例子 例子

    可看到能给属性正常赋值, 看一下底层

    // 第一个参数: 对象
    // 第二个参数: 标识符
    // 第三个参数: value
    // 第四个参数: 策略
    
    void
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    {
        _object_set_associative_reference(object, key, value, policy);
    }
    
    

    其中:
    第一个参数::要关联的对象,即给谁添加关联属性
    第二个参数:标识符,方便下次查找
    第三个参数:value
    第四个参数:属性的策略,nonatomic, atomic, retain, copy 等

    属性策略
    /**********************************************************************
    * Associative Reference Support
    **********************************************************************/
    
    id
    objc_getAssociatedObject(id object, const void *key)
    {
        return _object_get_associative_reference(object, key);
    }
    

    其实以前版本与现在版本底层变化很大, 但是 objc_setAssociatedObject, objc_getAssociatedObject名字不会变, 即API稳定

    _object_set_associative_reference

    看下_object_set_associative_reference内部实现, 看下关联对象做了哪些事情

    void
    _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
    {
        // 如果object &&  value 直接return 话说你不调不就行了么 :)
        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
        //  因为传入的可能是: 类名1, 类名2, 类名3 ... 转成一个统一类型方便后面处理
        //  disguised 方法是对ptr 一个处理
        DisguisedPtr<objc_object> disguised{(objc_object *)object};
     
        // 面向对象的处理
        // 包装 {policy, value} 为 ObjcAssociation
        ObjcAssociation association{policy, value};
    
        bool isFirstAssociation = false;
        {
            // 定义一个构造函数manager, 非单例
            AssociationsManager manager;
            // AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap。它是在`map_images`的时候初始化。
            AssociationsHashMap &associations(manager.get());
    
            // 传入的value
            if (value) {
                // 如果 value 存在
                // 将之前 包装disguised 插入 TheBucket,  
                // TheBucket = { first : second } =  { void * : ObjectAssociation }
                auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
                // 如果second存在即value存在
                if (refs_result.second) {
                    /* it's the first association we make */
                    // refs_result.second = true代表第一次  即首次关联, 
                    isFirstAssociation = true;
                }
    
                /* establish or replace the association */
                // 建立或取代 关联 留意下 这个时候associations还没有插入值
                auto &refs = refs_result.first->second;
                // 第二次 try_emplace 插入
                // key 与 association关联  
                // 这时候的key就是成员变量的key,将 association 插入桶中。有值的情况下没有插入,没有值的情况下才插入。
                auto result = refs.try_emplace(key, std::move(association));
                if (!result.second) {
                    // 第二次, 值会发生变化做新旧交换
                    association.swap(result.first->second);
                }
            } else {
                // 如果 value 不存在
                // 值不存在或者空这种直接清除 refs.erase(it);
                auto refs_it = associations.find(disguised);
                if (refs_it != associations.end()) {
                    // 找到对应的associations
                    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);
                        }
                    }
                }
            }
        }
        // 第一次时候标记对象是否有关联对象
        if (isFirstAssociation)
            object->setHasAssociatedObjects();
    
        // 释放
        association.releaseHeldValue();
    }
    

    关联对象设计图

    关联对象是运行时创建用于存储值 value, 看下关联对象的设计图

    关联对象

    关联对象流程分析

    • AssociationsManager : 是一个构造函数, 内部进行了加锁和解锁(不是单例)。 构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量

      AssociationsManager
      最下面的 加个static, 相当于类方法调用, 令mapstorage做初始化
    • AssociationsHashMap : 获取唯一的全局静态HashMap, 通过manager获取,是单例。是在map_images的时候进行的初始化。我们也可以读下他的结构

    AssociationsHashMap
    • value存在:

      • try_emplace创建空的TheBucket( { first : second } = { void * : ObjectAssociation } )
      • 用当前 policy 和 value 组成了一个 association 替换空的TheBucket
      • 首次关联标记。
      • refs_result.first->second获取object对应的map,此时map中还没有存储构造的association( policy - value )。
      • 第二次try_emplace将key与association传入TheBucket, 即传入map
      • 如果value不存在, association.swap将新旧值交换出来,存储新值进去, 后面会释放旧值。例如:
    • value不存在:

      • associations 根据对象包装disguised 找到编号: ref。
      • 根据key, 找到ref数据it, 并清除
      • 如果该对象已经没有关联对象了,则清除关联对象map
    • try_emplace :
      value有情况下 try_emplace 会走2次

      try_emplace会走2次
    • 第一次参数传入: DisguisedPtr<objc_object> disguised{(objc_object *)object} 闭包

    • 第二次参数传入: key, objc_setAssociatedObject传入的第二个参数自定义的key

    try_emplace 关系图
    • InsertIntoBucket
      插桶操作看一下内部
      InsertIntoBucket
      InsertIntoBucketImpl

    可看出内部实际是进行了查找,返回对应的 TheBucket。

    • LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3): 满足 负载因子 ( 3 / 4 ) 进行 2倍扩容。

    • LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones(): ( 总容量 - 已有元素) <= 总容量 / 8, 相当于 7 / 8 判断。

    • setHasAssociatedObjects 标记关联对象

      setHasAssociatedObjects
    • releaseHeldValue 释放旧对象

      releaseHeldValue

    关联对象实测流程

    关联对象赋值
    test_property 是我们设置的关联对象 关联对象打印
    打印一下associationvalue, 对应我们传的值(传入参数) result
    打印result, 初始second为true, first里面是其数据, 接下来走try_emplace进行插值 try_emplace
    try_emplace 中由于第一次 BucketT 没有值, 所以走下面插入新值 读result

    之后走第一次标记关联对象setHasAssociatedObjects以及释放旧value方法releaseHeldValue

    释放对象 setHasAssociatedObjects

    关联对象释放

    void objc_removeAssociatedObjects(id object) 
    {
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object, /*deallocating*/false);
        }
    }
    
    

    内部调用_object_remove_assocations, 看一下

    // Unlike setting/getting an associated reference,
    // this function is performance sensitive because of
    // raw isa objects (such as OS Objects) that can't track
    // whether they have associated objects.
    
    // 与设置/获取关联引用不同
    // 此函数对性能敏感,因为原始isa对象(如OS对象)无法跟踪它们是否具有关联对象。
    void
    _object_remove_assocations(id object, bool deallocating)
    {
        // 创建 空ObjectAssociationMap refs
        ObjectAssociationMap refs{};
    
        {
            // 创建 AssociationsManager
            AssociationsManager manager;
            // 创建 AssociationsHashMap
            AssociationsHashMap &associations(manager.get());
            // associations 中找到 iterator 迭代查询器
            AssociationsHashMap::iterator i = associations.find((objc_object *)object);
            
            // 迭代查询器 不等于 associations最后一个
            if (i != associations.end()) {
                
                // 将对象对应的 ObjectAssociationMap 存入refs临时空间
                refs.swap(i->second);
    
                // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
                // 如果我们没有取消分配,那么将保留 SYSTEM_OBJECT 关联对象。
                bool didReInsert = false;
                // 对象非释放的情况下
                if (!deallocating) {
                    for (auto &ref: refs) {
                        if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                            // 重新将系统的关联对象插入
                            i->second.insert(ref);
                            didReInsert = true;
                        }
                    }
                }
                
                // 对象释放的情况下
                if (!didReInsert)
                    // 清理迭代器i
                    associations.erase(i);
            }
        }
    
        // Associations to be released after the normal ones.
        // 将在正常关联之后释放关联。
        SmallVector<ObjcAssociation *, 4> laterRefs;
    
        // release everything (outside of the lock).
        // 释放所有
        for (auto &i: refs) {
            if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                // If we are not deallocating, then RELEASE_LATER associations don't get released.
                if (deallocating)
                    laterRefs.append(&i.second);
            } else {
                // 释放非系统的关联对象
                i.second.releaseHeldValue();
            }
        }
        
        // 循环释放 laterRefs
        for (auto *later: laterRefs) {
            later->releaseHeldValue();
        }
    }
    

    关联对象总结

    设值流程

    • 创建一个AssociationsManager 管理类
    • 获取唯一全局静态哈希Map
    • 判断是否存在关联对象值
      • 存在 :
        • 创建一个空的 ObjectAssociationMap 去取查询的键值对
        • 如果发现没有这个 key 就先插入一个 空的 BucketT
        • 标记对象存在关联对象
        • 用当前 策略 policy值 value 组成了一个 ObjcAssociation 替换之前空的BucketT
        • 标记 ObjectAssociationMap 为 第二次
      • 不存在 :
        • 根据DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
        • 清理迭代器

    取值流程

    • 创建一个 AssociationsManager 管理类
    • 获取唯一的全局静态哈希Map
    • 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
    • 如果这个 迭代查询器 != associations.end(), 即不是最后一个, 那么获取 : ObjectAssociationMap (这里有策略 policy值 value)
    • ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
    • 返回value

    相关文章

      网友评论

          本文标题:OC底层探索(十八): 类扩展和关联对象

          本文链接:https://www.haomeiwen.com/subject/xcjdprtx.html