美文网首页
底层原理:关联对象

底层原理:关联对象

作者: 飘摇的水草 | 来源:发表于2022-03-12 16:54 被阅读0次

在分类中声明属性的时候,只会自动生成方法的声明,并不会生成方法的实现和成员变量,举例说明:

#import "Person.h"

@interface Person (Test)

@property (assign, nonatomic) int weight;

- (void)setWeight:(int)weight;
- (int)weight;

@end

即通过上述代码声明 weight 属性时,其只会声明 setWeight: 方法和 weight 方法,却并不会自动生成这两个方法的实现和 _weight 变量,如果直接在头文件里声明成员变量则编译不通过。

手动实现属性效果

如果想自己实现类似属性的用法,可以用下面的方式来实现效果:

#import "Person.h"

@interface Person (Test)

@property (assign, nonatomic) int weight;

@end

@implementation Person (Test)

NSMutableDictionary *weights_;
+ (void)load
{
   weights_ = [NSMutableDictionary dictionary];
}

- (void)setWeight:(int)weight
{
   NSString *key = [NSString stringWithFormat:@"%p", self];
   weights_[key] = @(weight);
}

- (int)weight
{
   NSString *key = [NSString stringWithFormat:@"%p", self];
   return [weights_[key] intValue];
}
@end

这种方案的缺点是: weights_ 这个字典存在内存泄漏的问题。

关联对象

分类不能直接添加成员变量,但可以通过关联的方式间接添加成员变量。将传入的属性值和对象关联起来,所以叫关联对象,我们先来看下关联对象的实现方式

#import "Person.h"

@interface Person (Test)

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;

@end

#import "Person+Test.h"
#import <objc/runtime.h>

@implementation Person (Test)

static const void *nameKey = &nameKey;
static const void *weightKey = &weightKey;

- (void)setName:(NSString *)name
{
   objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
   return objc_getAssociatedObject(self, nameKey);
}

- (void)setWeight:(int)weight
{
   objc_setAssociatedObject(self, weightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)weight
{
      return [objc_getAssociatedObject(self, weightKey) intValue];
}
@end

此时我们即可像正常访问其他属性一样对 nameweight 属性进行取值和赋值

添加关联对象

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

移除所有关联对象

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

通过关联对象添加的成员变量在内存中不是存储在实例对象中,也不是存储在类对象中,原理后面有解读

key的设置方式

其中设置 key 可以有多种方式,一种是上面所示的那样通过如下方式:

static const void *nameKey = &nameKey;
static const void *weightKey = &weightKey;

这种方式的缺点一是比较繁琐,二是如果不加 static 关键字修饰,则可以在其他文件中通过 extern const void *nameKey 这种方式来访问 nameKeystatic 在这里的作用是表示仅限当前文件

第二种,可以通过下面这种

static const char *nameKey;

- (void)setName:(NSString *)name
{
   objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
   return objc_getAssociatedObject(self, &nameKey);
}

这种方案相比第一种,变量只占用一个字节的存储空间

第三种方式:

- (void)setName:(NSString *)name
{
   objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
   //_cmd == @selector(name)
   return objc_getAssociatedObject(self, @selector(name));
}
关联对象的原理

实现关联对象技术的核心:

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

其中Map同平时使用的字典相似,我们用下面这张图来演示这四者之间的关系:

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

AssociationsManager

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

AssociationsHashMap

我们继续查看 AssociationsHashMap 内部的源码,发现 AssociationsHashMap 继承自 unordered_map :

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

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

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

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

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

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

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

创建 AssociationsManager 类型的 manager,拿到 manager 内部的AssociationsHashMap,即 Associations

传入的 object 参数经过 DISGUISE 函数被转化为了 disguised_ptr_t 类型的disguised_object
其实 DISGUISE 函数内部只是对 object 做了位运算:

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

总结

  • 一个实例对象就对应一个 ObjectAssociationMap ,而 ObjectAssociationMap 里面存储着多个此实例对象的关联对象的 key 以及 ObjcAssociationObjcAssociation 中存储着关联对象的value和policy。
  • 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager
  • 如果设置关联对象为nil,就相当于是移除关联对象。
  • 这种毕竟不是属性,没有弱引用

源码的解读在objc4的objc-references.mm

相关文章

网友评论

      本文标题:底层原理:关联对象

      本文链接:https://www.haomeiwen.com/subject/ejmkdrtx.html