iOS进阶收藏ios

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

2019-07-23  本文已影响32人  劳模007_Mars

探索底层原理,积累从点滴做起。大家好,我是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执行的时候,底层会检查是否有关联对象。

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

更多技术知识请关注公众号
iOS进阶


iOS进阶.jpg
上一篇下一篇

猜你喜欢

热点阅读