iOS进阶笔记(二) 关联对象(Associate)

📣 iOS进阶笔记目录


一、什么情况下使用关联对象

如果类要添加属性,只有在objc_allocateClassPairobjc_registerClassPair之间class_addProperty。一旦类注册后,属性(property_list_t *baseProperties)被存在class_ro_t结构中,该结构不允许再被改变(class_ro_t为只读类型)。

可以在runtime运行时为类关联属性,将属性存在一张全局的HasMap中与原有类结构隔离。根据struct class_rw_ext_t property_array_t二维数组),可以查看runtime动态添加的属性及类原有属性。

二、关联对象的使用

先看下API

// 设置关联对象
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy);

// 获取关联对象
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);

// 移除关联对象
void objc_removeAssociatedObjects(id _Nonnull object);

给Person+Category 分类关联一个nickName属性

// Person+Category.h
@interface Person (Category)
@property (nonatomic, copy) NSString *nickName;
@end

// Person+Category.m
#import "Person+Category.h"
#import <objc/runtime.h>

@implementation Person (Category)
// 设置关联对象
- (void)setNickName:(NSString *)nickName {
    objc_setAssociatedObject(self, @selector(nickname), nickName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
// 获取关联对象
- (NSString *)nickName {
    return objc_getAssociatedObject(self, _cmd);
}
@end

关联引用的policy

定义如下:

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

objc_AssociationPolicy 含义
OBJC_ASSOCIATION_ASSIGN assin
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong, atomic
OBJC_ASSOCIATION_COPY copy, atomic

三、底层原理分析

首先看下几个关键类及结构关系

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

AssociationsManager结构

  • AssociationsManager管理一个自旋锁和一个单例AssociationsHashMap.

  • 在初始化(调用构造函数)时,会启动自旋锁。

  • 调用析构函数时,进行解锁。

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();
    }
};
AssociationsManager::Storage AssociationsManager::_mapStorage;
}

AssociationsHashMap结构

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

AssociationsHashMap是以DisguisedPtr<objc_object>为key,ObjectAssociationMap为Value的HashMap。

其中DisguisedPtr为范型,包装了关联的对象self

template <typename T>
class DisguisedPtr {
    uintptr_t value;
    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }

    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }
 public:
    DisguisedPtr() { }
    DisguisedPtr(T* ptr) 
        : value(disguise(ptr)) { }
    DisguisedPtr(const DisguisedPtr<T>& ptr) 
        : value(ptr.value) { }

    DisguisedPtr<T>& operator = (T* rhs) {
        value = disguise(rhs);
        return *this;
    }
    DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
        value = rhs.value;
        return *this;
    }
    operator T* () const {
        return undisguise(value);
    }
    T* operator -> () const { 
        return undisguise(value);
    }
    T& operator * () const { 
        return *undisguise(value);
    }
    T& operator [] (size_t i) const {
        return undisguise(value)[i];
    }
};

ObjectAssociationMap结构

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

ObjectAssociationMap是以objc_setAssociatedObject传入的key作为Map的key,ObjcAssociation作为value。(ObjcAssociation包装了objc_setAssociatedObject传入value和policy)


ObjcAssociation结构

class ObjcAssociation {
    uintptr_t _policy;// 内存管理policy
    id _value;// 真正关联的value
}

ObjcAssociation包装了objc_setAssociatedObject传入value和policy


1、objc_setAssociatedObject调用过程

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


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<objc_object> disguised {
        (objc_object *)object
    };
    
    // 初始化一个关联类,包装了policy 和 value
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();// 根据不同的policy执行retain或copy操作
    bool isFirstAssociation = false;
    // 出大括号,释放内存
    {
        // 初始化AssociationsManager,即调用AssociationsManager的构造函数,开启了一个自旋锁
        // 通过manager管理锁和一个单例AssociationsHashMap
        AssociationsManager manager;
        // associations为一个全局map,以disguised为key,ObjectAssociationMap为value
        // ObjectAssociationMap也是一个map,以传入的传入的keyz作为map的key,ObjcAssociation为value
        AssociationsHashMap &associations(manager.get());
       if (value) {
            // refs_result类型为 pair
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            // refs_result.second != 0,说明不止一个ObjectAssociationMap,将isFirstAssociation标志位置为true
            if (refs_result.second) {
                isFirstAssociation = true;
            }
            auto &refs = refs_result.first->second;// 当前ObjectAssociationMap最后一个ObjcAssociation
            // 向associations存入新association,通过move函数在存入后又将association清空
            // std::move详见 http://www.cplusplus.com/reference/utility/move/?kw=move
            
            auto result = refs.try_emplace(key, std::move(association));
            
            if (!result.second) {// 只有一个association时,取出ObjcAssociation并与当前association交换
                association.swap(result.first->second);
            }
        } else {//如果传的是空值,则移除关联
            // 根据disguised找到相应的ObjectAssociationMap
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                // 再根据key取出ObjcAssociation
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);// 交换association
                    refs.erase(it);// 擦除ObjcAssociation
                    if (refs.size() == 0) {
                        associations.erase(refs_it);// 擦除装ObjcAssociation的Map 即ObjectAssociationMap

                    }
                }
            }
        }
    }

    if (isFirstAssociation)
        object->setHasAssociatedObjects();
    association.releaseHeldValue();
}


这里再解释下上边代码提到DenseMap的try_emplace函数。

  • 该函数会通过传入key经过LookupBucketFor进行查找是否已存在BucketT(即传入的第二个参数,BucketT为包装了key 和 value的盒子);

  • 若存在则直接返回一个由迭代器(通过makeIterator构造一个iterator-- DenseMap为自定义容器,不支持标准的迭代器, 因此需要实现自定义的迭代器)和false组成的std::pair(该类型类似于元组(Truple),可以通过firstsecond进行取值。)

  • 若不存在,则新建一个迭代器和ture组成的pair。

  template <typename... Ts> std::pair<iterator, bool> try_emplace(KeyT &&Key, Ts &&... Args) {
    // BucketT为包装了key 和 value的盒子
    BucketT *TheBucket;// 根据传入的key,查找对应的BucketT
    if (LookupBucketFor(Key, TheBucket))
      //若存在BucketT, 返回makeIterator(TheBucket, getBucketsEnd(), true)和false组成的pair
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Otherwise, insert the new element.
    // 若不存在BucketT,则加入新的BucketT
    TheBucket =
        InsertIntoBucket(TheBucket, std::move(Key), std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }
  
  iterator makeIterator(BucketT *P, BucketT *E,
                        bool NoAdvance=false) {
    // 返回一个迭代器
    return iterator(P, E, NoAdvance);
  }

小结:

  • 通过AssociationsManager操作 AssociationsHashMap(通过get()获取AssociateionsHashMap)

  • AssociationsHashMap通过DisguisedPtr 绑定一个ObjectAssociationMap;
    一个object对应一个ObjectAssocationMap

  • ObjectAssocationMap通过key绑定一个ObjectAssociation

  • ObjectAssociation包装了value和policy

  • 设置关联对象为nil,作用相当于移除关联对象。

objc_removeAssociatedObjects函数说的很清楚。


2、objc_getAssociatedObject

取值比较简单:需要遍历两层HashMap,首先通过object找到对应的ObjectAssociationMap;
再通过key找到ObjectAssociationMap中的ObjcAssociation,也就找到了_value

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

    {
        AssociationsManager manager;
        
        AssociationsHashMap &associations(manager.get());
        // 通过object为key,找到associations(HashMap)中对应的ObjectAssociationMap。通过迭代器遍历
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            // 在以key为键,查找ObjectAssociationMap中对应的ObjcAssociation,同样也是使用迭代器进行遍历
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;// 找到了对应的ObjcAssociation
                association.retainReturnedValue();// 对ObjcAssociation中的_value进行内存管理
            }
        }
    }

    return association.autoreleaseReturnedValue();// 将_value加入自动释放池,将其释放
}

3、objc_removeAssociatedObjects

objc_removeAssociatedObjects会移除关联对象中所有的association

具体源码:

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

void object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        // 根据object找到相应的ObjectAssociationMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);// 将i中的association存入临时Map中

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);// 若没有释放,会重新插入ObjcAssociation
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);// 擦除value(ObjectAssociationMap)
        }
    }

    //巧妙的设计:laterRefs起到释放association缓冲作用。若当前正在释放association(忙不过来了),则将其它的association装入laterRefs向量中等待后续释放。
    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();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

// objc-reference.mm
// 释放掉ObjcAssociation中的 `_value`
inline void releaseHeldValue() {
    if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
        objc_release(_value);
    }
}

三、一些问题?

1、可以为分类添加属性么?(上面已经解释比较清楚了)

答:不可以,但是可以通过对象关联属性。具体是在runtime运行时,将属性存在一张全局的HasMap中与原有类结构隔离。根据struct class_rw_ext_t property_array_t二维数组),可以查看runtime动态添加的属性及类原有属性。

2、关联对象内存管理问题,关联的属性需要手动移除么?

答:不需要,通过上面objc_getAssociatedObject函数执行过程也可以看出,获取关联对象的属性后会将其加入自动释放池,等待被释放。具体会在对象dealloc时一起被释放。


相关依据如下:dealloc过程
dealloc() -> _objc_rootDealloc(id obj) objc_object::rootDealloc() -> object_dispose(id obj) -> objc_destructInstance(id obj) -> associations.erase()

主要函数:

// 销毁对象时调用
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();// 若有析构函数,则说明是C++
        bool assoc = obj->hasAssociatedObjects();// 判断是否有对象关联引用

        // This order is important.
        if (cxx) object_cxxDestruct(obj);// 若为C++,则直接调用其析构函数执行对象的释放
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);// 若有关联对象,则先移除关联对象
        obj->clearDeallocating();// 销毁对象
    }
    return obj;
}

四、AssociationsHashMap延伸

查看AssociationsHashMap和ObjectAssociationMap本质是DenseMap类型

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

再查了下DenseMap(llvm-DenseMap.h

由于发现DenseMap是定义在llvm里面,于是就去llvm官网上找了下关于DenseMap的相关介绍

核心概括:DenseMap是一个简单的二次探测哈希表,擅长于小类型的键值对操作。增删元素会导致DenseMap扩容/缩减, 进而引起迭代器失效. DenseMap最小要求分配64个元素, 如果map中存储元素较少造成很大的浪费。


再来看下源码定义,初步分析DenseMap有四个成员Buckets(保存BucketT的数组)、NumEntries(即已使用的条目个数)、NumTombstones(表示被废弃且未重新映射的条目个数)、NumBuckets(当前Buckets个数)

template <typename KeyT, typename ValueT,
          typename ValueInfoT = DenseMapValueInfo<ValueT>,
          typename KeyInfoT = DenseMapInfo<KeyT>,
          typename BucketT = detail::DenseMapPair<KeyT, ValueT>>
class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>,
                                     KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> {
  friend class DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

  // Lift some types from the dependent base class into this class for
  // simplicity of referring to them.
  using BaseT = DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

  BucketT *Buckets;// 保存BucketT的数组,实际也是数组的首地址,指向第一个元素
  unsigned NumEntries;// 即已使用的条目个数
  unsigned NumTombstones;// 表示被废弃且未重新映射的条目个数.
  unsigned NumBuckets;// 即当前Buckets个数

public:
  /// Create a DenseMap wth an optional \p InitialReserve that guarantee that
  /// this number of elements can be inserted in the map without grow()
  explicit DenseMap(unsigned InitialReserve = 0) { init(InitialReserve); }

  DenseMap(const DenseMap &other) : BaseT() {
    init(0);
    copyFrom(other);
  }

  DenseMap(DenseMap &&other) : BaseT() {
    init(0);
    swap(other);
  }

  template<typename InputIt>
  DenseMap(const InputIt &I, const InputIt &E) {
    init(std::distance(I, E));
    this->insert(I, E);
  }

  DenseMap(std::initializer_list<typename BaseT::value_type> Vals) {
    init(Vals.size());
    this->insert(Vals.begin(), Vals.end());
  }

  ~DenseMap() {
    this->destroyAll();
    operator delete(Buckets);
  }

  void swap(DenseMap& RHS) {
    std::swap(Buckets, RHS.Buckets);
    std::swap(NumEntries, RHS.NumEntries);
    std::swap(NumTombstones, RHS.NumTombstones);
    std::swap(NumBuckets, RHS.NumBuckets);
  }
 ..... somecode 
}

文末推荐两篇DeseMap写的很全面的博客《llvm中的数据结构及内存分配策略 - DenseMap》《LLVM笔记(19) - ADT介绍(二) DenseMap》


以上(完)


----------End------------
posted @ 2021-08-04 20:54  ITRyan  阅读(486)  评论(0编辑  收藏  举报