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】
浙公网安备 33010602011771号