Runtime - Associated Objects (关联对象) 的实现原理
主要围绕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
值:
. 声明 static char kAssociatedObjectKey; // 使用 &kAssociatedObjectKey 作为 key 值;
. 声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; // 使用 kAssociatedObjectKey 作为 key 值;
. 用 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(, 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(), _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() == ) 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 = , // 0000 0000
OBJC_ASSOCIATION_RETAIN_NONATOMIC = , // 0000 0001
OBJC_ASSOCIATION_COPY_NONATOMIC = , // 0000 0011
// 我们只看后8位, 从第0位开始算
OBJC_ASSOCIATION_RETAIN = , // 0111 1001
OBJC_ASSOCIATION_COPY = // 0111 1011
};
这个是底层的策略
// 有人很好奇怎么跟 objc_AssociationPolicy 不一样,其实objc_AssociationPolicy是根据NONATOMIC把runtime的OBJC_ASSOCIATION_SETTER 细分了, 仔细研究你就会发现objc_AssociationPolicy 与OBJC_ASSOCIATION_SETTER 有很大的联系的 enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = ,
OBJC_ASSOCIATION_SETTER_RETAIN = ,
OBJC_ASSOCIATION_SETTER_COPY = , // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = ( << ),
OBJC_ASSOCIATION_GETTER_RETAIN = ( << ),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = ( << )
};
我们可以看出下面代码在判断关联策略的时候都是 & 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 = , // 0000 0000 & 1111 1111 = 0000 0000
OBJC_ASSOCIATION_RETAIN_NONATOMIC = , // 0000 0001 & 1111 1111 = 0000 0001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001
OBJC_ASSOCIATION_COPY_NONATOMIC = , // 0000 0011 & 1111 1111 = 0000 0011 & OBJC_ASSOCIATION_SETTER_COPY (0000 0011) = 0000 0011
// 我们只看后8位, 从第0位开始算
OBJC_ASSOCIATION_RETAIN = , // 0111 1001 & 1111 1111 = 0111 1001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001
OBJC_ASSOCIATION_COPY = // 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;
}
1波三折, 大量的搜索之后我们终于找到了答案, 其实对象销毁前 最终会调用objc_destructInstance 函数, 其内部做了判断如果有关联对象会调用 _object_remove_assocations函数, 而_object_remove_assocations 函数内部把被关联对象的所有关联对象值对象 进行release 并且释放销毁当前被关联对象.
就写到这里, 如果那里写的不对请留言指正, 我们一起共同进步, 谢啦
相关参考技术blog
C++ : https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html
其他有关Associated Objects文章:
https://www.jianshu.com/p/48b1b04d0b87
Runtime - Associated Objects (关联对象) 的实现原理的更多相关文章
- iOS Category 添加属性实现原理 - 关联对象
iOS Category 添加属性实现原理 - 关联对象 RunTime为Category动态关联对象 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系.对象一开始初始化的时候其属性 ...
- OC - runtime 之关联对象
header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...
- iOS开发--Runtime的简单使用之关联对象
一.Runtime关联对象的方法简介: 在<objc/runtime.h>中,有三个关联的方法,分别是: objc_setAssociatedObject objc_getAssociat ...
- Runtime之成员变量&属性&关联对象
上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用.本篇将讨论实现细节的相关内容. 在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@e ...
- AssociatedObject关联对象原理实现
介绍 关联对象(AssociatedObject)是Objective-C 2.0运行时的一个特性,允许开发者对已经存在的类在扩展中添加自定义的属性.在实际生产过程中,比较常用的方式是给分类(Cate ...
- Runtime - 关联对象使用方法及注意点
大家都知道在分类里,可以间接的添加属性,运用runtime关联对象. 如下图,只是声明了btnClickedCount的set, get方法而已 并没有生成_btnClickedCount 成员变量, ...
- 【OC底层】AssociatedObject 关联对象
如何实现给分类“添加成员变量”? 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中.但可以通过关联对象来间接实现 关联对象提供了以下API 1> 添加关联对象 void objc_s ...
- Effective Objective-C 2.0 — 第10条:在既有类中使用关联对象存放自定义数据
可以通过“关联对象”机制来把两个对象连起来 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系” 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于 ...
- ios 关联对象运用 objc_setAssociatedObject
点按钮的时候,给alertView添加一个关联对象(被点击这个按钮), objc_setAssociatedObject(alert, &kRepresentedObject, sender, ...
随机推荐
- hdu2138 Miller_Rabin
Description Give you a lot of positive integers, just to find out how many prime numbers there are ...
- 51nod 1785 数据流中的算法 | STL的应用
51nod 1785 数据流中的算法 题面 动态求平均数.方差.中位数. 题解 这道题的坑: 平均数在答案中是向下取整输出并在后面添加".00" 方差:平方的平均数减去平均数的平方 ...
- 20135319zl字符集报告
字符集实验 ASCII 首先,查找ZHULI五个字符对应的ASCII码,5a 48 55 4c 49. 然后,用vim打开一个空文档,按下":",输入%!xxd 然后,输入 000 ...
- poj 3415 后缀数组 两个字符串中长度不小于 k 的公共子串的个数
Common Substrings Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 11469 Accepted: 379 ...
- 科学计算三维可视化---Mayavi入门(Mayavi介绍和安装)
Mayavi介绍 是基于VTK开发的可视化软件(更加高效),Mayavi完全由python编写,方便使用,而且可以使用python编写扩展,嵌入到用户程序中 安装要求 VTK >pip3 ins ...
- 科学计算三维可视化---TVTK入门(安装与测试)
推文:http://docs.huihoo.com/scipy/scipy-zh-cn/tvtk_intro.html 推文:http://code.enthought.com/pages/mayav ...
- Kafka 0.8 Producer处理逻辑
Kafka Producer产生数据发送给Kafka Server,具体的分发逻辑及负载均衡逻辑,全部由producer维护. 1.Kafka Producer默认调用逻辑 1.1 默认Partiti ...
- go build 不同系统下的可执行文件
Golang 支持在一个平台下生成另一个平台可执行程序的交叉编译功能. 1.Mac下编译Linux, Windows平台的64位可执行程序: 1 2 $ CGO_ENABLED=0 GOOS=linu ...
- 图论:LCA-欧拉序
#include<cmath> #include<vector> #include<cstdio> #include<cstring> #include ...
- yum安装_yum命令的相关操作
2017年1月11日, 星期三 yum安装的四种方式 一.默认:从国外下载 二.国内:从阿里获取 http://mirrors.aliyun.com 1. cd /etc/yum.repos.d 2 ...