主要围绕3个方面说明runtime-Associated Objects (关联对象)
1. 使用场景
2.如何使用
3.底层实现
3.1 实现原理
3.2 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?
3.3 关联对象的五种关联策略有什么区别,有什么坑?
3.3 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?
- 本文所使用的源码为 objc4-723
1. 使用场景
我们知道哈,在 Objective-C 中可以通过 Category 给一个现有类/系统类添加方法,但是Objective-C与Swift 不同的是不能添加实例变量, 然而我们可以通过runtime - Associated Objects(关联对象) 来弥补这一不足.
2. 如何使用
思路其实很简单, 都知道在 Objective-C里面类的属性其实在编译的时候, 编译器都会转换成setter/getter方法, 所以我们就直接声明/实现属性的setter/getter方法, 我们来看看代码怎么写吧.
2.1 相关函数
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); // 为object对象关联以key为键value值, 传入 nil 则可以移除已有的关联对象 id objc_getAssociatedObject(id object, const void *key); // 读取object对象key键对应的值 void objc_removeAssociatedObjects(id object); // 移除object关联所有对象
2.1.1 key 值
关于前两个函数中的 key
值是我们需要重点关注的一个点,这个 key
值必须保证是一个唯一常量。一般来说,有以下三种推荐的 key
值:
1. 声明 static char kAssociatedObjectKey; // 使用 &kAssociatedObjectKey 作为 key 值; 2. 声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; // 使用 kAssociatedObjectKey 作为 key 值; 3. 用 selector; // 使用 getter/setter 方法的名称作为 key 值, 还可以省一个变量.
那种写法都可以, 看个人喜好
2.1.2 关联策略 policy
关联策略 等价属性 说明
OBJC_ASSOCIATION_ASSIGN @property (assign) or @property (unsafe_unretained) 弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (strong, nonatomic) 强引用关联对象,且为非原子操作
OBJC_ASSOCIATION_COPY_NONATOMIC @property (copy, nonatomic) 复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN @property (strong, atomic) 强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY @property (copy, atomic) 复制关联对象,且为原子操作
2.2 如何使用
下面我们对NSString 进行关联一个 tempValue 属性
声明 NSString+TTExtension.h 文件
#import <Foundation/Foundation.h> @interface NSString (TTExtension) - (NSString *)tempValue; // getter - (void)setTempValue:(NSString *)tempValue; // setter @end
实现 NSString+TTExtension.m 文件
#import "NSString+TTExtension.h" #import <objc/runtime.h> static char dm_associated_key_id; @implementation NSString (TTExtension) - (NSString *)tempValue { return objc_getAssociatedObject(self, &dm_associated_key_id);} - (void)setTempValue:(NSString *)tempValue { objc_setAssociatedObject(self, &dm_associated_key_id, tempValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
3. 底层实现
3.1 实现原理
3.1.1 objc_setAssociatedObject 设置关联对象
我们可以在 objc-runtime.mm
文件中找到 objc_setAssociatedObject
函数, 我们会发现这个函数内部调用了 _object_set_associative_reference 函数
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); }
接着可以在 objc-references.mm
文件中找到 _object_set_associative_reference 函数
/** * 关联引用对象 ** Objective-C: objc_setAssociatedObject 底层调用的是 objc_setAssociatedObject_non_gc(有的版本是直接调用object_set_associative_refrence) ** 底层: objc_setAssociatedObject_non_gc调用了_object_set_associative_reference * object : 关联对象 * key : 可以认为是一个属性名 * value : 值 * policy : 关联策略 */ void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); // 创建一个关联 id new_value = value ? acquireValue(value, policy) : nil; // acquireValue判断关联策略类型 { AssociationsManager manager; // 用 mutex_t(互斥锁)维护的AssociationsHashMap哈希表映射; 哈希表可以认为就是个dictionary AssociationsHashMap &associations(manager.associations()); // 获取到所有的关联对象 disguised_ptr_t disguised_object = DISGUISE(object); // 把object对象包装成disguised_ptr_t类型 if (new_value) { // break any existing association. // 遍历所有的被关联对象查找 object 对象对应的被关联对象 AssociationsHashMap::iterator i = associations.find(disguised_object); /// end()返回指向map末尾的迭代器, 并非最优一个元素. 如果想获取最后一个元素需要last=end()之后last--,last就是最后一个元素 if (i != associations.end()) { // i != (末尾迭代器) 代表object之前有过关联 // secondary table exists ObjectAssociationMap *refs = i->second; // 获取键值对i对应的value值, ObjectAssociationMap哈希表映射 ObjectAssociationMap::iterator j = refs->find(key); // 遍历所有的关联策略查找 key 对应的关联策略 if (j != refs->end()) { // 存在 old_association = j->second; // 缓存之前的值, 在下面会release这个值 j->second = ObjcAssociation(policy, new_value); // 把{(}最新值&关联策略}更新 } else { // 不存在 (*refs)[key] = ObjcAssociation(policy, new_value); // 不存在直接根据key赋值{值&关联策略} } } else { // 不存在, 代表当前对象没有关联过对象, 所以直接创建ObjectAssociationMap储存{值&关联策略} // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; // 创建ObjectAssociationMap associations[disguised_object] = refs; // 储存 ObjectAssociationMap (*refs)[key] = ObjcAssociation(policy, new_value); // 创建关联策略 // 储存关联策略 object->setHasAssociatedObjects(); // data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS; } } else { // setting the association to nil breaks the association. // 代表赋值的为nil AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // 存在, 代表之前关联过 ObjectAssociationMap *refs = i->second; // 找到 对象对应的ObjectAssociationMap ObjectAssociationMap::iterator j = refs->find(key); // 遍历查找是否有过赋值 if (j != refs->end()) { // 如果有需要释放之前的对象 old_association = j->second; // 缓存之前的value对象, 后期释放 refs->erase(j); // 移除j元素 } } } } // release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association); // release之前的关联对象对应的value }
备注: win32不考虑
AssociationsManager
一个维护全部关联记录单例类.
// 一个单例类, 维护全部的关联内容 class AssociationsManager { // associative references: object pointer -> PtrPtrHashMap. static AssociationsHashMap *_map; // 静态的说明维护所有的对象的关联对象 public: AssociationsManager() { AssociationsManagerLock.lock(); } // mutex_t 互斥锁: 加锁 ~AssociationsManager() { AssociationsManagerLock.unlock(); } // mutex_t 互斥锁: 解锁 AssociationsHashMap &associations() { // 是一个无序的哈希表, 维护了从对象地址到 ObjectAssociationMap 的映射; 我们发现了它还是一个懒加载的对象 if (_map == NULL) _map = new AssociationsHashMap(); return *_map; } }; AssociationsHashMap *AssociationsManager::_map = NULL; // 全局的说明所有的关联是一个单利对象管理
AssociationsHashMap
无序 hash 表,key 是被关联对象的地址. value 是 ObjectAssociationMap.
// 可以理解为所有被关联对象的全部关联记录; class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };
ObjectAssociationMap
也是一个无序 hash 表,key 是关联对象设置的 key, value 是 ObjcAssociation
// 可以理解为一个被关联对象的所有关联对象 class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };
ObjcAssociation
1个关联对象的封装,里面维护了关联对象的 value 和 关联策略
//一个关联对象的封装 class ObjcAssociation { uintptr_t _policy; // 关联策略 id _value; // 关联值 public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} uintptr_t policy() const { return _policy; } id value() const { return _value; } bool hasValue() { return _value != nil; } // 关联对象是否有值 };
objc_setAssociatedObject(object, kAssociatedKey, value, policy); AssociationsManager -> { lock // 锁保证数据的安全 AssociationsHashMap -> { // 无序 hash 表 &object = ObjectAssociationMap -> { // 无序 hash 表 kAssociatedKey = ObjcAssociation - > { // 关联对象的封装 value, policy } } } unlock }
3.1.2 objc_getAssociatedObject
我们可以在 objc-runtime.mm
文件中找到 objc_getAssociatedObject 函数, 我们会发现这个函数内部调用了_object_get_associative_reference 函数
id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, (void *)key); }
接着在 objc-references.mm
文件中找到_object_get_associative_reference 函数
/** 不多解释. 看完_object_set_associative_reference看这个很简单 * 关联引用对象 ** Objective-C: objc_getAssociatedObject 最终会调用 _object_get_associative_reference来获取关联对象。 ** 底层: _object_get_associative_reference * object : 被关联对象 * key : 可以认为是一个属性名 */ id _object_get_associative_reference(id object, void *key) { id value = nil; uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); } } } } if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { objc_autorelease(value); } return value; }
3.1.3 objc_removeAssociatedObjects
在 objc-runtime.mm
文件中找到 objc_removeAssociatedObjects 函数, 我们会发现这个函数内部调用了_object_remove_assocations 函数
void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { // 判断当前对象是否有关联对象 _object_remove_assocations(object); } }
在 objc-references.mm
文件中找到_object_remove_assocations 函数
/** * 移除关联对象 ** Objective-C: objc_removeAssociatedObjects 底层调用的是 _object_remove_assocations ** 底层: _object_remove_assocations * object : 被关联对象 */ void _object_remove_assocations(id object) { // 全部的关联策略 vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); if (associations.size() == 0) return; // map中元素的个数为0直接return disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // copy all of the associations that need to be removed. ObjectAssociationMap *refs = i->second; for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) { elements.push_back(j->second); } // remove the secondary table. delete refs; // 释放refs内存 associations.erase(i); // 从associations 移除object } } // the calls to releaseValue() happen outside of the lock. // 遍历移除被关联对象中所有的 AssociationsHashMap 对象,并且对全部对象release for_each(elements.begin(), elements.end(), ReleaseValue()); }
3.2 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?
其实看过AssociationsManager 类之后你就已经知道答案了, 关联的对象都存放在一个维护关联的单例类里// 一个单例类, 维护全部的关联内容 class AssociationsManager { // associative references: object pointer -> PtrPtrHashMap. static AssociationsHashMap *_map; // 静态的说明维护所有的对象的关联对象 public: AssociationsManager() { AssociationsManagerLock.lock(); } // mutex_t 互斥锁: 加锁 ~AssociationsManager() { AssociationsManagerLock.unlock(); } // mutex_t 互斥锁: 解锁 AssociationsHashMap &associations() { // 是一个无序的哈希表, 维护了从对象地址到 ObjectAssociationMap 的映射; 我们发现了它还是一个懒加载的对象 if (_map == NULL) _map = new AssociationsHashMap(); return *_map; } }; AssociationsHashMap *AssociationsManager::_map = NULL; // 全局的说明所有的关联是一个单利对象管理
3.3 关联对象的五种关联策略为什么与底层的策略不一样?
这个是关联对象暴露的策略typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // 0000 0000 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 0000 0001 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 0000 0011 // 我们只看后8位, 从第0位开始算 OBJC_ASSOCIATION_RETAIN = 01401, // 0111 1001 OBJC_ASSOCIATION_COPY = 01403 // 0111 1011 };
// 有人很好奇怎么跟 objc_AssociationPolicy 不一样,其实objc_AssociationPolicy是根据NONATOMIC把runtime的OBJC_ASSOCIATION_SETTER 细分了, 仔细研究你就会发现objc_AssociationPolicy 与OBJC_ASSOCIATION_SETTER 有很大的联系的 enum { OBJC_ASSOCIATION_SETTER_ASSIGN = 0, OBJC_ASSOCIATION_SETTER_RETAIN = 1, OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. OBJC_ASSOCIATION_GETTER_READ = (0 << 8), OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8) };
我们可以看出下面代码在判断关联策略的时候都是 & 0xFF
/** * 判断关联值类型 * value : 值 * policy : 关联策略 */ static id acquireValue(id value, uintptr_t policy) { switch (policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: return objc_retain(value); // retain value case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); // copy object } return value; }
而 objc_AssociationPolicy & 0xFF之后 再 & OBJC_ASSOCIATION_SETTER_* 会怎么样呢请看
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // 0000 0000 & 1111 1111 = 0000 0000 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 0000 0001 & 1111 1111 = 0000 0001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 0000 0011 & 1111 1111 = 0000 0011 & OBJC_ASSOCIATION_SETTER_COPY (0000 0011) = 0000 0011 // 我们只看后8位, 从第0位开始算 OBJC_ASSOCIATION_RETAIN = 01401, // 0111 1001 & 1111 1111 = 0111 1001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001 OBJC_ASSOCIATION_COPY = 01403 // 0111 1011 & 1111 1111 = 0111 1011 & OBJC_ASSOCIATION_SETTER_COPY (0000 0011) = 0000 0011 };
& 0xFF 之后我们会发现
OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_RETAIN 其实最后 再 & OBJC_ASSOCIATION_RETAIN_NONATOMIC 居然就是 OBJC_ASSOCIATION_RETAIN_NONATOMIC, 所以虽然不一样但是其实是有内在联系的
而 OBJC_ASSOCIATION_COPY_NONATOMIC 和 OBJC_ASSOCIATION_COPY 其实也是一样的
OBJC_ASSOCIATION_GETTER_* 就不解释了其实也一样, 只不过我还没找到在什么位置用到, 如果你知道请留言
3.3 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?
这个问题我们就得看看一个对象对销毁之后调用了上面, 都知道对象销毁会调用 - (void)dealloc {} 方法, 那么我们就看看runtime原码是怎么调用的我们可以在 NSObject.mm
文件中找到 dealloc 函数, 可是dealloc 又调用了_objc_rootDealloc 函数
- (void)dealloc { _objc_rootDealloc(self); }
_objc_rootDealloc 又调用了 对象内部的rootDealloc方法
// 释放内存, dealloc发起 void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); // objc-object.h }
// 释放内存 inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; object_dispose((id)this); // objc-runtime-new.m }
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); // 移除关联对象, 释放value obj->clearDeallocating(); } return obj; }
就写到这里, 如果那里写的不对请留言指正, 我们一起共同进步, 谢啦
相关参考技术blog
C++ : https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html
其他有关Associated Objects文章:
https://www.jianshu.com/p/48b1b04d0b87