关联对象实现原理

前言

提到关联对象不得不让我们想到两个概念:
1.分类 category

  • 专门用来给类添加新的方法
  • 不能给类添加成员变量和属性,添加了成员变量,也无法获取到
  • 注意:可以通过 关联对象的方式给类动态添加属性
  • 分类中用 @property 定义变量,只会生成变量的 gettersetter 方法的声明,不能生成方法的实现和带下划线的成员变量。

2.类扩展 extension

  • 可以说成是特殊的分类,也称作匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法
    他们的主要区别是分类是通过runtime运行时动态添加到类中,而类扩展是在编译期,直接生成的set,get方法,而内存的开辟是在类被实例化时候做的。

通过分类添加属性实现代码

@property (nonatomic, copy) NSString *like_name;
- (void)setLike_name:(NSString *)like_name {
    objc_setAssociatedObject(self, "like_name", like_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

objc_setAssociatedObject 分析

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy){
    _object_set_associative_reference(object, key, value, policy);
}
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    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));

    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};

    // 在锁外保留新值(如果有的话)。
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* 这是我们建立的第一个联想 */
                isFirstAssociation = true;
            }

            /* 建立或替换关联 */
            auto &refs = refs_result.first->second;
            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);

                    }
                }
            }
        }
    }

    // 在锁外部调用setHasAssociatedObjects,因为这个
    // 将调用对象的_noteAssociatedObjects方法,如果是
    // 有一个,这可能触发+初始化,那会怎样呢
    // 任意的东西,包括设置更多的关联对象
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // 释放旧值(锁外)
    association.releaseHeldValue();
}

forbidsAssociatedObjects

// 类不允许在其实例上关联对象
#define RW_FORBIDS_ASSOCIATED_OBJECTS       (1<<20)

bool forbidsAssociatedObjects() {
        return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
    }

AssociationsManager

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    // 静态变量 全局唯一
    static Storage _mapStorage;
public:
    //加锁 避免多线程重复创建
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
    static void init() {
        _mapStorage.init();
    }
};
AssociationsHashMap 的数据结构:
AssociationsHashMap

try_emplace
  // 如果键尚未在映射中,则将键值对插入映射中
  // 如果键不在映射中,则就地构造值,否则
  // 它没有移动
  template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // 已经在表上

    // 否则,插入新元素
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

下面我们重点看下 try_emplace 的一个流程:

  /// LookupBucketFor—为Val查找适当的bucket,并将其返回
  /// FoundBucket。如果bucket包含键和值,则返回
  /// true,否则返回一个带有空标记或墓碑和的桶
  /// returns false.
  template<typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    const BucketT *BucketsPtr = getBuckets();
    const unsigned NumBuckets = getNumBuckets();

    if (NumBuckets == 0) {
      FoundBucket = nullptr;
      return false;
    }

    // FoundTombstone -跟踪我们是否在探测时找到了一个墓碑
    const BucketT *FoundTombstone = nullptr;
    const KeyT EmptyKey = getEmptyKey();
    const KeyT TombstoneKey = getTombstoneKey();
    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
           !KeyInfoT::isEqual(Val, TombstoneKey) &&
           "Empty/Tombstone value shouldn't be inserted into map!");

    //哈希函数 得到 下标
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
    unsigned ProbeAmt = 1;
    while (true) {
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // 发现Val的桶吗?如果是,返回它
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        FoundBucket = ThisBucket;
        return true;
      }

      // 如果我们发现一个空的桶,则该键不存在于集合中
      // 插入它并返回默认值
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // 如果我们在探测的时候已经看到了墓碑,那就把它填上
        // 我们最终探测到的空桶
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false;
      }

      // 如果这是墓碑,记住它。如果瓦尔没有出现在地图上,我们
      // 宁愿返回它而不是需要更多探测的东西
      // 对于零值也是如此
      if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
          !FoundTombstone)
        FoundTombstone = ThisBucket;  // Remember the first tombstone found.
      if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
        FoundTombstone = ThisBucket;

      // 否则,它是一个哈希冲突或墓碑,继续二次
      // 探索
      if (ProbeAmt > NumBuckets) {
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      BucketNo += ProbeAmt++;
      BucketNo &= (NumBuckets-1);
    }
  }

// 虽然两个方法名字都一样,但是,通过参数不同,可以发现方法内部调用了上面的 LookupBucketFor

  template <typename LookupKeyT>
  bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    const BucketT *ConstFoundBucket;
    bool Result = const_cast<const DenseMapBase *>(this)
      ->LookupBucketFor(Val, ConstFoundBucket);
    FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
    return Result;
  }

获取关联对象值

objc_getAssociatedObject
id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

_object_get_associative_reference

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        // 通过 object 找到 ObjectAssociationMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            // 通过 key 找到 ObjcAssociation
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

关联对象总结

一个全局的数据集合 AssociationHashMap ,一个对象会对应一个 ObjectAssociationMap,而该对象所关联的属性,都已键值对的形式存储在 ObjectAssociationMap中。其实就是两层哈希map , 存取的时候两层处理(类似二维数组)

关联对象的销毁

在对象被销毁时会调用 dealloc 方法,同时 会对 关联对象进行释放。

rootDealloc->object_dispose->objc_destructInstance
objc_destructInstance
/***********************************************************************
* objc_destructInstance
* 在不释放内存的情况下销毁实例
* c++调用析构函数
* 调用ARC ivar清理
* 删除关联的引用
* 返回的obj。如果' obj '为nil,则不执行任何操作
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // 一次读取所有标志以提高性能
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // 这个顺序很重要
        if (cxx) object_cxxDestruct(obj);
        // 删除关联的引用
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

_object_remove_assocations

// 与设置/获取相关引用不同
// 这个函数是性能敏感的,因为
// 不能跟踪的原始isa对象(如OS对象)
// 是否有关联的对象
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);

            //如果我们没有解除分配,那么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)
                associations.erase(i);
        }
    }

    // 在正常的关联之后才会被释放
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // 释放(锁外的)所有东西
    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();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

设值流程

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 判断是否插入的关联值是否存在:
  • 3.1: 存在走第4步
  • 3.2: 不存在就走 : 关联对象插入空流程
  • 4: 创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 5: 如果发现没有这个 key 就插入一个 空的 BucketT进去 返回
  • 6: 标记对象存在关联对象
  • 7: 用当前 修饰策略 和 值 组成了一个 ObjcAssociation 替换原来 BucketT 中的空
  • 8: 标记一下 ObjectAssociationMap 的第一次为 false

如果插入空值会根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器并置空

取值流程

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 4: 如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value)
  • 5: 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 6: 返回_value
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值