iOS进阶笔记(二) 关联对象(Associate)
一、什么情况下使用关联对象
如果类要添加属性,只有在objc_allocateClassPair
与objc_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),可以通过first
和second
进行取值。) -
若不存在,则新建一个迭代器和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------------
(限于水平,本文可能存在瑕疵甚至错误的地方。如有发现,还请留言指正,相互学习。thx! )
KEEP LOOKING, DON`T SETTLE!