OC关联对象的底层原理解析

什么是关联对象

所谓关联对象就是,让一个对象和一个值(通常也是一个对象或者一个指针)通过给定的key和association policy(关联策略)进行关联。association policy实际是内存管理策略,是一个枚举,它的定义如下:

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

常用的关联对象设置和读取两个接口的声明如下:

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

除了关联策略枚举objc_AssociationPolicy,没有其他什么特殊的数据结构。

关联对象的使用

关联对象一般用于运行时给某个对象设置关联,最常用的就是在Category中设置属性时,通过关联对象实现属性的setter和getter方法点击了解Category相关的内容。接下来我们创建一个自定义类,并实现一个Category,演示关联对象的使用:


/*Extension*/
@interface TestObject()

@property(nonatomic, strong) NSString *ext_name;
- (void)ext_testMethod;

@end


const static NSString *kExtName;

@implementation TestObject

- (void)testMethod{
    NSLog(@"cate_name:%@",self.cate_name);
}

@end


@implementation TestObject (Cate)

- (void)setCate_name:(NSString *)cate_name
{
    NSLog(@"设置self和值的关联");
    objc_setAssociatedObject(self, &kExtName, cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)cate_name
{
    NSLog(@"self读取关联的值");
    return objc_getAssociatedObject(self, &kExtName);
}

- (void)cate_testMethod{
    self.cate_name = @"你哈";
}
@end

首先set方法通过调用objc_setAssociatedObject让self和cate_name进行关联,通过kExtName的地址&kExtName作为key值,关联策略OBJC_ASSOCIATION_RETAIN_NONATOMIC表示retain,表示cate_name会被强引用。在get方法里面通过objc_getAssociatedObject读取关联的值。
通过关联对象的使用,我们发现他的存取操作很类似与哈希表的存取操作。那么它底层的结构到底是什么样的呢?

关联对象的底层原理

问题:

关联对象是如何将属性和对象关联的?
关联对象是如何存储的?
关联对象何时会被释放?

关联对象的底层结构

关联对象底层是通过一个二维哈希表来存储的。第一层的哈希表AssociationsHashMap是以对象指针(指针的伪装)为key,value为ObjectAssociationMap,ObjectAssociationMap是以objc_setAssociatedObject传入的key和value构成的哈希表,
关联对象的底层数据结构可以总结如下图所示:

结构图.jpg
  • DenseMap
    图中的AssociationsHashMap和ObjectAssociationMap都是DenseMap类型是哈希表:
class DenseMap : public DenseMapBase,
                                     KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> {
  friend class DenseMapBase;

  // Lift some types from the dependent base class into this class for
  // simplicity of referring to them.
  using BaseT = DenseMapBase;

  BucketT *Buckets;
  unsigned NumEntries;
  unsigned NumTombstones;
  unsigned NumBuckets;
...
}
  • AssociationsManager
class AssociationsManager {
    using Storage = ExplicitInitDenseMap, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
};
typedef DenseMap, ObjectAssociationMap> AssociationsHashMap;

这里边首先注意的一点是AssociationsManager不是单利。而是他的属性static Storage _mapStorage是个单利。_mapStorage其实就是一个AssociationsHashMap,关联对象都存在这个表里。

  • AssociationsHashMap
typedef DenseMap ObjectAssociationMap;

AssociationsHashMap中的DisguisedPtr里的objc_object其实就是object,DisguisedPtr顾名思义就是伪装指针,它的作用是获取object的指针,然后做一下伪装处理得到一个值,以这个值作为第一层哈希Map的Key。

  • ObjectAssociationMap
typedef DenseMap, ObjectAssociationMap> AssociationsHashMap;

ObjectAssociationMap中的const void *就是objc_setAssociatedObject的key参数,ObjcAssociation是用来接收policy和value的,这样第二层哈希Map就建立了这个key和value的映射关系。
这样设计的好处是:第一层保证可以有多个对象object被关联,第二层保证了每个对象可以关联多个key和value。

插入流程源码解析

通过源码解析了解关联对象底层原理。我们直接通过源码的调用逻辑来进行分析。其调用流程是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 disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    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) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* 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);

                    }
                }
            }
        }
    }

    // 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();
}

1、首先对关联的对象指针进行包装,得到disguised:

DisguisedPtr disguised{(objc_object *)object};

2、创建ObjcAssociation的实例用于接收policy和value,通过acquireValue方法对value进行内存管理:

ObjcAssociation association{policy, value};
acquireValue();
//acquireValue的实现
inline void acquireValue() {
        if (_value) {
            switch (_policy & 0xFF) {
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break;
            }
        }
    }

根据policy,对value进行retain或者copy操作,如果是assign,则不处理。
3、接下来就创建一个管理类AssociationsManager。

AssociationsManager manager;

AssociationsManager包含一个_mapStorage静态变量,这个静态变量是为了实现单利,存储全局唯一的二维哈希表AssociationsHashMap,用于所有关联对象的存储。

4、读取全局唯一的关联对象二维哈希表AssociationsHashMap:

AssociationsHashMap &associations(manager.get());

有了这个表之后,就看进入关联对象如何插入associations表的过程。
5、判断value如果不为空:
5.1、创建一个空的ObjectAssociationMap,去查询是否有已经关联的键值对表:

auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});

5.2、如果是第一次插入数据,则记录isFirstAssociation状态:

if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }

5.3、如果已经存在则直接返回;如果不存在则新建一个Bucket
(ObjectAssociationMap),然后返回:

  template 
  std::pair try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Otherwise, insert the new element.
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }

5.4、插入数据:

 /* 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);
            }

6、如果value不为空
6.1、根据DisguisedPtr找到AssociationHashMap中的iterator迭代查询器;

 auto refs_it = associations.find(disguised);

6.2、清理迭代器;

 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);

                    }
                }
            }

7、如果是第一次插入数据,标记对象已被关联:

if (isFirstAssociation)
        object->setHasAssociatedObjects();

8、关联新的value需要对旧的value进行释放:

    // release the old value (outside of the lock).
    association.releaseHeldValue();

总结:以上的插入流程可以总结为如下
1、创建一个AssociationsMaanger管理类;
2、获取全局唯一的静态哈希Map;
3、判断当前插入的关联值(value)是否存在。如果存在,走关联值value不为空的流程;如果不存在,走关联值value为空的流程
4、关联值value不为空的流程:

4.1、创建一个空的ObjectAssociationMap去取查询键值对;
4.2、如果发现没有这个key,就插入一个空的Bucket进去,也就是第一步的ObjectAssociationMap,第一次正常都是空的,然后返回;
4.3、标记对象存在关联对象;
4.4、用当前修饰策略和值组成ObjcAssociation替换原来BuckeT中的空值;
4.5、标记一下ObjectAssociationMap的第一次为false;

5、关联值value为空的流程:

5.1、根据DisguisedPtr找到AssociationHashMap中的iterator迭代查询器;
5.2、清理迭代器;
备注:这里要注意的一点是,当value为空,而key不为空,它其实就是一个删除操作。

关联对象何时被删除

关联对象在对象被释放时会被清理,详情参考iOS内存管理底层原理;

你可能感兴趣的:(OC关联对象的底层原理解析)