iOS底层原理探索—关联对象的本质

探索底层原理,积累从点滴做起。大家好,我是Mars。

往期回顾

iOS底层原理探索—OC对象的本质
iOS底层原理探索—class的本质
iOS底层原理探索—KVO的本质
iOS底层原理探索— KVC的本质
iOS底层原理探索— Category的本质(一)
iOS底层原理探索— Category的本质(二)

今天继续带领大家探索iOS之关联对象的本质。

我们在iOS底层原理探索— Category的本质(一)中提到过在iOS中分类不能直接添加成员变量。在分类中添加的属性并不会帮助我们自动生成成员变量,只会生成setget方法的声明,需要我们自己去实现。,下面我们验证一下:

我们创建一个本类Person,在类中声明int类型的age属性,然后创建一个Person的分类,并在分类中声明int类型的weight属性,然后在main.m文件中导入分类的头文件,创建一个Person的对象,给ageweight属性赋值,并且打印两个属性值:

测试代码.png

通过编译运行,我们发现程序崩溃,崩溃信息就是就找不到PersonsetWeight方法。而且程序在分类中报出两个警告,找不到Person分类的setWeight方法和weight方法,从而验证我们上面的结论。

分类属性崩溃信息.png

分类声明属性警告.png

这时可能会有人提出,既然系统没有为我们生成setget方法的实现,那我们手动实现setget方法的实现不就可以了吗?我们继续验证一下这个方法可不可行:

我们在分类的.m文件中声明全局变量_weight,并实现setget方法,然后运行程序:

手动实现set方法.png

通过运行我们发现确实完成了赋值并打印出结果,但是这样有一个问题不知道大家有没有发现,我们声明的全局变量_weight,无论对象创建与销毁,只要程序在运行_weight变量就存在。如果我们再创建一个新的Person对象,给weight赋不同的值,那么之前我们创建的person对象的weight属性的值也会改变:

声明全局变量后测试.png

通过打印看到,我们新创建一个Person对象student,赋值后打印之前创建的person对象属性值发现,person对象的weight属性的值已经改变了。

手动添加setget方法实现的办法失败了,那我们可不可以通过runtime的一些方式间接实现添加成员变量的效果呢?答案是可以的,下面就给大家介绍关联对象的方法。

关联对象

在OC中,runtime提供了动态添加属性和获得属性的API:objc_setAssociatedObjectobjc_getAssociatedObject,下面我们用代码来演示一下具体使用方法:

关联对象语法测试.png

通过关联对象我们很容易就实现了给分类添加成员变量,我们来分析一下具体的使用。

添加关联对象

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

/*  参数解读
* object : 给哪个对象添加属性。这里要给自己添加属性,用self。
* key : 属性名,根据key获取关联对象的属性的值。传入一个指针即可。在objc_getAssociatedObject中通过次key获得属性的值并返回。
* value : 关联的值,也就是通过set方法传入的值给属性。
* policy : 策略,属性以什么形式保存。
*/

其中参数policy对应的是枚举值:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    // 指定一个弱引用相关联的对象    
    OBJC_ASSOCIATION_ASSIGN = 0,      
    // 指定相关对象的强引用,非原子性
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    // 指定相关的对象被复制,非原子性    
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
    // 指定相关对象的强引用,原子性    
    OBJC_ASSOCIATION_RETAIN = 01401,  
    // 指定相关的对象被复制,原子性       
    OBJC_ASSOCIATION_COPY = 01403     
};

分别对应的修饰符为:


objc_AssociationPolicy.png

获得关联对象

objc_getAssociatedObject(id object, const void *key);
/*  参数解读
* object : 获取哪个对象里面的关联的属性。
* key : 属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。

移除所有关联对象

- (void)removeAssociatedObjects
{
   // 移除所有关联对象
   objc_removeAssociatedObjects(self);
}

至此我们就了解了关联对象以及如何使用关联对象。下面我们从底层源码来进一步了解关联对象的实现原理。

关联对象实现原理

添加关联对象

我们在runtime源码中找到objc_setAssociatedObject函数:

objc_setAssociatedObject.png

函数内部调用_object_set_associative_reference函数,我们继续追踪_object_set_associative_reference函数的实现:

_object_set_associative_reference.png

通过红框标注我们找到了4个对象,其实这4个对象就是实现关联对象技术的核心对象:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation

我们分别进入这4个对象内部查看它们之间的关系:

AssociationsManager

通过AssociationsManager内部源码发现,其内部有一个AssociationsHashMap对象:

AssociationsManager.png

AssociationsHashMap

我们继续查看AssociationsHashMap内部的源码。发现AssociationsHashMap继承自unordered_map(均由红框标注):

AssociationsHashMap.png

我们看到里面还包括黄框标注的ObjectAssociationMap。我们先继续查看unordered_map内部源码:
unordered_map内源码.png

unordered_map源码中我们可以看出_Key_Tp也就是前两个参数对应着map中的KeyValue,那么对照上面AssociationsHashMap内源码发现_Key中传入的是disguised_ptr_t_Tp传入的值则为ObjectAssociationMap*(AssociationsHashMap图中用红线标注)。

同样我们来到黄框标注ObjectAssociationMap中,发现ObjectAssociationMap中同样也是奖ObjcAssociation以参数形式传入,并以keyValue的方式存储着ObjcAssociation(AssociationsHashMap图中用黄线标注)。

我们继续来到ObjcAssociation源码中查看:

ObjcAssociation.png

我们发现ObjcAssociation存储着_policy_value,而这两个值我们发现正是我们调用添加关联对象objc_setAssociatedObject函数传入的值。也就是说我们在调用objc_setAssociatedObject函数中传入的valuepolicy这两个值最终是存储在ObjcAssociation中的。

现在我们已经对AssociationsManagerAssociationsHashMapObjectAssociationMapObjcAssociation四个对象之间的关系有了简单的认识,那么接下来我们回到objc_setAssociatedObject源码,具体查看一下objc_setAssociatedObject函数中传入的四个参数究竟做了哪些操作:

执行顺序.png

图中1标注,首先根据传入的value经过acquireValue函数处理获取new_valueacquireValue函数内部其实是通过对策略的判断返回不同的值:
acquireValue.png

图中2标注,创建AssociationsManager类型的manager,拿到manager内部的AssociationsHashMap,即associations
图中3标注,传入的object参数经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object
其实DISGUISE函数内部只是对object做了位运算:
DISGUISE.png

图中4标注,我们看到被处理成new_valuevalue,和policy一起被存入了ObjcAssociation中。而ObjcAssociation对应我们传入的key被存入了ObjectAssociationMap中。disguised_objectObjectAssociationMap则以key-value的形式对应存储在associations中也就是AssociationsHashMap中。

如果我们value传入nil的话则会执行图中5标注的代码。

我们将以上分析用一张示意图来展示:


objc_setAssociatedObject示意图.png

分析到这里我们可以总结出:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap里面存储着多个此实例对象的关联对象的key以及ObjcAssociationObjcAssociation中存储着关联对象的valuepolicy
我们也可以得出结论:关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性

获得关联对象

我们进入objc_getAssociatedObject函数内部查看,发现其内部调用的是_object_get_associative_reference函数:

objc_getAssociatedObject.png

进入_object_get_associative_reference函数查看:
_object_get_associative_reference.png

分析源码可知,先拿到AssociationsManager中的AssociationsHashMap,在里面查找disguised_object,即我们传入的object参数,如果存在就取出ObjectAssociationMap,并在ObjectAssociationMap中查找key对应的value,即ObjcAssociation,然后再ObjcAssociation中取出valuepolicy,最后返回value

移除关联对象

通过调用objc_removeAssociatedObjects用来删除所有的关联对象。其内部调用的是_object_remove_assocations函数。我们直接查看_object_remove_assocations函数内部源码:

_object_remove_assocations.png

大概逻辑就是遍历所有ObjectAssociationMap,然后进行delete删除操作。

当然,即使我们没有调用移除关联对象的方法,在对象销毁的时候,对应的关联对象也会被销毁。因为在dealloc执行的时候,底层会检查是否有关联对象。

至此我们就完成了关联对象的底层探索,如有疑问欢迎大家留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容