iOS底层原理之分类关联对象

610 阅读3分钟

准备工作

我们定义一个LGPerson的类,继承于NSObject,添加LGPerson的分类LGPerson+LGA,在LGPerson+LGA分类中添加属性name,实现其setget方法,代码如下 代码执行结果如下所示

重点分析

我们从实现分类属性的代码方法中可以发现setget方法才是重点,而其中的objc_setAssociatedObjectobjc_getAssociatedObject才是重中之重。

objc_setAssociatedObject分析

我们从源码中查找objc_setAssociatedObject

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

SetAssocHook.get()相当于接口模式,对外暴露接口,提供给外部使用,我们继续查看SetAssocHook从中我们可以看到SetAssocHook调用了_base_objc_setAssociatedObject函数,我们再来查看_base_objc_setAssociatedObject函数_base_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));
    //包装对象
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //包装对象(policy,value)
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();
    //此处代码折叠起来
    {
        AssociationsManager manager;
        
        AssociationsHashMap &associations(manager.get());

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }

            /* establish or replace the association */
            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);

                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}
  • object转成objc_object对象
  • 装配对象
  • 我们看下acquireValue函数根据policy给属性添加的是copy还是retain
  • AssociationsManager是个析构函数,可以声明多个不是唯一的
  • AssociationsHashMap全局唯一哈希表,我们从上面可以看出AssociationsHashMap是通过_mapStorage.get()获得,而_mapStorage是通过static void init()初始化得到,由于使用了static,因此AssociationsHashMap全局唯一
  • 查看哈希表是何时赋值的此时associations还没有值;此时打印associations已经由数据了,很明显是第181行的代码原因。
  • 我们再看try_emplace函数从上图中我们可以得出通过关键字key以及桶子TheBucket来查找是否已经在哈希表中存在,如果存在则返回,否则就插入
  • 如果是第一次插入,则设置相关信息设置相关信息
  • 查看LookupBucketFor函数
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 - Keep track of whether we find a tombstone while probing.
    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;
      // Found Val's bucket?  If so, return it.
      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
        FoundBucket = ThisBucket;
        return true;
      }

      // If we found an empty bucket, the key doesn't exist in the set.
      // Insert it and return the default value.
      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
        // If we've already seen a tombstone while probing, fill it in instead
        // of the empty bucket we eventually probed to.
        FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
        return false;
      }

      // If this is a tombstone, remember it.  If Val ends up not in the map, we
      // prefer to return it than something that would require more probing.
      // Ditto for zero values.
      if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
          !FoundTombstone)
        FoundTombstone = ThisBucket;  // Remember the first tombstone found.
      if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
        FoundTombstone = ThisBucket;

      // Otherwise, it's a hash collision or a tombstone, continue quadratic
      // probing.
      if (ProbeAmt > NumBuckets) {
        FatalCorruptHashTables(BucketsPtr, NumBuckets);
      }
      BucketNo += ProbeAmt++;
      BucketNo &= (NumBuckets-1);
    }
  }

整个方法就是TheBucket查找过程,这个过程和cache_t的方法查找相似,都是循环查找,找不到平移知道找到或者查完为止

设置值总结

插入非空值 插入空值

objc_getAssociatedObject分析

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

objc_getAssociatedObject函数执行了_object_get_associative_reference

_object_get_associative_reference

找到则返回当前的值

取值总结

总结