目录
_object_set_associative_reference 方法
前言
之前已经分析了类和分类的加载,那么这篇文章来讲解一下类扩展和关联对象的底层原理
类扩展
类扩展又称为匿名的分类,可以给当前类添加属性和方法
类扩展的本质
写一个类扩展,生成cpp文件,并在文件中查找扩展中添加的属性
可以看到cpp文件中存在带下划线的该成员变量,并且合成了set和get方法,所以说扩展中可以给类添加属性。
再看看扩展中添加的方法:
可以看到扩展中方法已经添加在方法列表中了,也就是说扩展中添加的方法在编译过程中,就已经添加到了methodlist
中,作为类的一部分,即编译时期直接添加到本类
里面
关联对象
关联对象底层原理的实现,主要就是两个部分:
通过
objc_setAssociatedObject
设值流程通过
objc_getAssociatedObject
取值流程
设值流程
在分类中重写属性cate_name的set、get方法,通过runtime的属性关联方法实现
这里objc_setAssociatedObject
方法有四个参数,分别表示:
参数1:要关联的对象,即给谁添加关联属性
参数2:标识符,方便下次查找
参数3:value
参数4:属性的策略,即
nonatomic、atomic、assign
等
这里objc_setAssociatedObject
源码实现中调用_object_set_associative_reference 方法。
_object_set_associative_reference 方法
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
//相当于包装了一下 对象object,便于使用
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value}; // 包装一下 policy - value
// retain the new value (if any) outside the lock.
association.acquireValue();//根据策略类型进行处理
bool isFirstAssociation = false;
{
//初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一
AssociationsManager manager2;
AssociationsHashMap &associations2(manager2.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对
if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
/* it's the first association we make */
isFirstAssociation = true;//标记true
}
/* establish or replace the association */
//得到一个空的桶子,找到引用对象类型,即第一个元素的second值
auto &refs = refs_result.first->second;
//查找当前的key是否有association关联对象
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {//如果结果不存在
association.swap(result.first->second);
}
} else { //如果传的是空值,则移除关联,相当于移除
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();//释放
}
通过_object_set_associative_reference 源码可知方法主要分为以下几步:
创建一个 AssociationsManager 管理类
获取唯一的全局静态哈希Map:AssociationsHashMap
判断 value 是否存在(value为空则走清除逻辑)
尝试插入对象桶(ObjectAssociationMap):以
disguised(object)
(通过类得到的,不同的类名该函数返回值不同) 为 key,尝试插入一个空的ObjectAssociationMap
(如果没有的话)。插入桶成功(第二层 map 空)时,插入一个空桶(桶是 key→ObjcAssociation)(key 是用户传进来的 key(
void*
),value 是封装了policy + value
的ObjcAssociation
)第一次插入对象时,通过 setHasAssociatedObjects 设置标记位(如果是这个 object 第一次有关联对象,就设置
isa
标志位的has_assoc = 1
)用 ObjcAssociation 替换旧值(如果之前已有 key)(如果第二层已经有这个 key,不是第一次插入,就调用
swap
)标记第一次插入 ObjectAssociationMap 的布尔值为 true
这里AssociationsManager
类型的变量,会自动调用AssociationsManager
的析构函数进行初始化,这个源码中加锁是为了避免多线程重复创建,但并不代表唯一
而AssociationsHashMap类型的哈希map,这个是唯一的:
可以看到源码是通过_mapStorage.get()生成哈希map,而_mapStorage是一个静态变量,所以哈希map永远是通过静态变量获取的,所以全场唯一
如果传入的value是空值,那么走else流程,else分支中移除关联
try_emplace方法
try_emplace方法负责完成对全局哈希map中插入新的类以及在类对应的表中插入新的键值对。
这里的桶子负责保存键值之间的映射关系,可以理解为保存值,而这里保存的值的类型取决于方法的调用者:
这里第一次调用try_emplace方法时,是在对全局的哈希表和类操作,可以理解为桶子里存的是类,第二次调用try_emplace方法时,是在对类对应的哈希表和关联对象操作。可以理解为桶子里存的是关联对象。
可以得到大致结构是这样的:
AssociationsManager
可以有多个,通过AssociationsManagerLock
锁可以得到一个AssociationsHashMap
类型的map
。
而map
中有很多的关联对象map
,类型是ObjectAssociationMap
,其中key
为DisguisedPtr<objc_object>
,例如TCJPerson
会对应一个ObjectAssociationMap
,TCJStudent
也会对应一个ObjectAssociationMap
.
ObjectAssociationMap
哈希表中有很多key-value
键值对,其中key
的类型为const void *
,其实这个key
从底层这个方法_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
的参数就可以看出,key
是我们关联属性时设置的字符串
,value
的类型为ObjcAssociation
。其中ObjcAssociation
是用于包装policy
和value
的一个类。
以上流程总结如下图:
取值流程
与objc_setAssociatedObject
类似,关联对象get方法的核心是objc_getAssociatedObject
objc_getAssociatedObject
通过源码可知,主要分为以下几部分
1:创建一个
AssociationsManager
管理类2:获取唯一的全局静态哈希
Map
:AssociationsHashMap
3:通过
find
方法根据DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器4:如果这个迭代查询器不是最后一个 获取 :
ObjectAssociationMap
(policy和value
)5:通过
find
方法找到ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value
6:返回
value
关于类扩展和分类
category 类别、分类
专门用来给类添加新的方法
不能给类添加成员属性,添加了成员属性,也无法取到
注意:其实可以通过
runtime
给分类添加属性,即属性关联
,重写setter
、getter
方法
分类中用
@property
定义变量,只会生成变量的setter
、getter
方法的声明
,不能生成方法实现和带下划线的成员变量
extension 类扩展
可以说成是
特殊的分类
,也可称作匿名分类
可以
给类添加成员属性
,但是是私有变量
可以
给类添加方法
,也是私有方法