014*:分类和类扩展和关联属性-(AssociationsHashMap-ObjectAssociationMap)
关联对象本质
第一层:类名object:bucket【AssociationsHashMap】
第二层:key:ObjcAssociation(value和policy)【ObjectAssociationMap】
1: Category分类与Extension拓展的区别
1 :Category:类别,分类
- 专门用来给类
添加
新的方法
不能
给类添加成员属性
,添加了也取不到。- 分类中用
@property
定义的变量,只会生成变量的getter
和setter
方法,不能生成方法实现
和带下划线
的成员变量
。
成员属性
不可添加:@interface HTPerson(CatA) { NSString * catA_name; // 不可这样添加 }
property属性
可添加:
@interface HTPerson(CatA) @property (nonatomic, copy) NSString *prop_name; @end
编译器可读取
到名称
。表示有getter
和setter
方法的声明。没有get和set方法的实现
2:类扩展
与其说成是特殊的分类
,已称作匿名分类
可以
给类添加成员属性
、属性
、方法
,但都是私有
的
拓展必须添加在@interface声明
和@implementation实现
之间:
Extension拓展
与@interface声明
是一样的作用,但是Extension拓展
中的成员变量
、属性
、方法
都是私有
的。- 可以通过
clang
,查看编译结果
进行验证
。Extension类拓展
的下划线成员变量
、函数
等,都直接加入
了本类
的相关位置
,完成
相应实现
。
Category
中的属性
如何用runtime
实现?A: 在属性的get
和set
方法实现内,动态添加关联对象
:
// CatA分类 #import <objc/runtime.h> // 本类 @interface HTPerson : NSObject @property (nonatomic, copy) NSString *name; @end @implementation HTPerson @end // CatA分类 @interface HTPerson (CatA) @property (nonatomic, copy) NSString *catA_name; // 属性 @end @implementation HTPerson(CatA) - (void)setCatA_name:(NSString *)catA_name { // 给属性`catA_name`,动态添加set方法 objc_setAssociatedObject(self, "catA_name", catA_name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)catA_name { // 给属性`catA_name`,动态添加get方法 return objc_getAssociatedObject(self, "catA_name"); } @end
参数解读:
- 动态
设置
关联属性:objc_setAssociatedObject
(关联对象
,关联属性key
,关联属性value
,策略
) - 动态
读取
关联属性:objc_getAssociatedObject
(关联对象
,关联属性key
)
2. 关联对象
1:关联对象如何运作的
static void _base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); } static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject}; objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { SetAssocHook.get()(object, key, value, policy); }
2:通过set方法得到get方法对象 _base_objc_setAssociatedObject
调用get()
,就是读取内容
。所以:可以直接写成
_base_objc_setAssociatedObject(object, key, value, policy);
3:_object_set_associative_reference
static void _base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); }
4:_object_set_associative_reference
进入_object_set_associative_reference
源码实现
关于关联对象 底层原理的探索 主要是看value存
到了哪里, 以及如何取出value
,以下是源码
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 DisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用 // 包装一下 policy - value ObjcAssociation association{policy, value}; // retain the new value (if any) outside the lock. association.acquireValue();//根据策略类型进行处理 //局部作用域空间 { //初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化 AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的 AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一 if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对 if (refs_result.second) {//判断第二个存不存在,即bool值是否为true /* it's the first association we make 第一次建立关联*/ object->setHasAssociatedObjects();//nonpointerIsa ,标记位true } /* establish or replace the association 建立或者替换关联*/ auto &refs = refs_result.first->second; //得到一个空的桶子,找到引用对象类型,即第一个元素的second值 auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有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); } } } } } // release the old value (outside of the lock). association.releaseHeldValue();//释放 }
acquireValue()
更具传递进来的polic
对值进行处理
inline void acquireValue() { if (_value) { switch (_policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: _value = objc_retain(_value); break; case OBJC_ASSOCIATION_SETTER_COPY: _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); break; } } }
因为AssociationsManager方法里看到AssociationsHashMap是通过_mapStorage.get()方法获得,而_mapStorage是通过static声明的,是静态变量,也就是AssociationsHashMap是通过静态变量_mapStorage获取的,所以是全场唯一的。
通过源码可知,主要分为以下几部分:
-
1:创建一个
AssociationsManager
管理类 -
2:获取
唯一
的全局静态哈希Map:AssociationsHashMap
-
3:判断是否插入的
关联值value
是否存在-
3.1:存在走第4步
-
3.2:不存在就走 :
关联对象-插入空流程
-
-
4:通过
try_emplace
方法,并创建一个空的ObjectAssociationMap
去取查询的键值对: -
5:如果发现
没有
这个key
就插入一个 空的 BucketT
进去并返回true -
6:通过
setHasAssociatedObjects
方法标记对象存在关联对象
即置isa指针
的has_assoc
属性为true
-
7:用当前
policy 和 value
组成了一个ObjcAssociation
替换原来BucketT 中的空
-
8:标记一下
ObjectAssociationMap
的第一次
为false
DisguisedPtr
和ObjcAssociation
分别对入参object
、policy
和value
进行了包装。
DisguisedPtr
和ObjcAssociation
分别对入参object
、policy
和value
进行了包装。
查看DisguisedPtr
结构,只有一个value
。 所以实际是将入参object
对象给到DisguisedPtr
对象的value
,包装记录一下。
查看ObjcAssociation
结构,只有_policy
和_value
。 所以实际是将入参policy
策略和value
新值给到ObjcAssociation
对象,包装记录一下。
1: AssociationsHashMap
内有多个类对
key-value结构,而每个类
对应的value
,又包含多个关联属性对
key-value结构。
2: 所以我们不管插入
还是移除
,都是先通过类
信息找到相应的类对
,再从类对
的value
中,通过关联属性key
找到对应的关联属性
,进行相应操作。
关联对象本质
第一层:类名object:bucket【AssociationsHashMap】
第二层:key:ObjcAssociation(value和policy)【ObjectAssociationMap】