美文网首页
关联对象-给Category添加属性

关联对象-给Category添加属性

作者: Goose的小黄花 | 来源:发表于2019-07-16 15:38 被阅读0次

我们知道,分类无法添加成员变量,在分类中定义了属性,系统没有生成对应的成员变量,也没有实现set和get方法。那我们如何实现为分类添加属性呢?

通过runtime中提供的关联对象相关API我们可以实现以上功能。

添加关联对象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)

获得关联对象
id objc_getAssociatedObject(id object, const void * key)

移除所有的关联对象
void objc_removeAssociatedObjects(id object)

关联API对象参数说明:
参数一:id object : 给哪个对象添加属性。
参数二:const void * key: 关联对象中属性值存取过程中对应唯一标识,根据key来设置和取值。
参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。
参数四:objc_AssociationPolicy 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     // 指定相关的对象被复制,原子性   
};

提供一个实例,给大家看看关联对象的基本使用。
定义Person类,以及Person+Test分类,在Person+Test中定义name ,weight属性,然后通过关联对象实现set和get方法,看外部能不能正常使用?

@interface Person : NSObject

@end

@implementation Person

@end

@interface Person (Test)

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int weight;

@end

@implementation Person (Test)
/*
 给key设值有三种比较好的的方法
 因为key值类型为const void *,任何类型的值,其实就是给一个唯一的标识
 
 1.我们针对每个属性,定义一个全局的key名,然后取其地址,着一定是唯一的
 加上static,只在文件内部有效
 static const void *NameKey = &NameKey;
 static const void *WeightKey = &WeightKey;
 
 2.针对每个属性,因为类中的属性名是唯一的,直接拿属性名作为key
 #define NameKey = @"name";
 #define WeightKey = @"weight";
 
 3.使用@seletor作为key
 直接用属性名对应的get方法的selector,有提示不容易写错
 并且get方法隐藏参数_cmd可以直接用,看上去就会更加简洁
 以下实例代码就是用的第三种方式
 
 */

- (void)setName:(NSString *)name{
    //通过一个key给对象关联一个值
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    //通过一个key,取出对象所关联的值
    //隐式参数 _cmd = @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setWeight:(int)weight{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (int)weight{
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

@end

外部调用
Person *person = [[Person alloc] init];
person.name = @"jack";
person.weight = 20;
        
NSLog(@"name:%@ weight:%d",person.name,person.weight);

打印结果
2019-07-15 17:52:27.471526+0800 给分类添加属性-关联对象[19511:8022622] name:jack weight:20

通过以上实例可以看出通过关联对象是可以成功为分类添加属性的。

那么使用关联对象是将属性添加到原有类的内存中,还是另外通过其他方式保存呢?如果通过其他方式,又是如何保存,如何销毁的呢?

关联对象原理

实现关联对象的核心对象有

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation
    其中Map就相当于我们平时使用的字典,也是key-value存取值。

我们通过runtime源码来分析底层原理,来到objc-references.mm,搜索objc_setAssociatedObjectobjc_getAssociatedObjectobjc_removeAssociatedObjects,分别来看看设置,取值,移除所有关联对象的底层逻辑。

/**********************************************************************
* Associative Reference Support
**********************************************************************/

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}


void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}


void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
_object_set_associative_reference函数
_object_set_associative_reference函数

_object_set_associative_reference函数内部我们可以找到上面说过的实现关联对象技术的核心对象。接下来我们来AssociationManager、AssociationsHashMap、ObjectAssociationMap、ObjcAssociation之间的关系。

AssociationManager
AssociationsManager源码
AssociationsHashMap、ObjectAssociationMap、ObjcAssociation
AssociationsHashMap和ObjectAssociationMap源码

通过AssociationsHashMap内部源码我们发现AssociationsHashMap继承自unordered_map首先来看一下unordered_map内的源码

unordered_map部分源码

从unordered_map源码中可以看出前两个参数_key,_tp对应的就是map中的key和value,参照传入参数,_key对应的值就是disguised_ptr_t,_Tp 对应的值就是ObjectAssociationMap *

接着我们查看ObjectAssociationMap,继承至map,和unordered_map类似,同样以key,value方式存储着ObjectAssociation。_key对应着void *,_Tp 对应着ObjectAssociation

再接着来查看ObjcAssociation。


ObjcAssociation源码

我们发现ObjcAssociation存储着_policy_value,而这两个值我们可以发现正是我们调用objc_setAssociatedObject函数传入的值,也就是说我们在调用objc_setAssociatedObject函数中传入的value和policy这两个值最终是存储在ObjcAssociation中的。

现在我们已经对AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四个对象之间的关系有了简单的认识,那么接下来我们来细读源码。


_object_set_associative_reference函数精读

最后通过一张图整理其中关系,这样就看的更加清晰明了。


关联对象原理图

关联对象并不是存储在关联对象本身内存中
关联对象存储在全局的一个AssociationsManager中
设置关联对象为nil,就相当于移除了关联对象

_object_get_associative_reference函数

_object_get_associative_reference函数

_object_remove_assocations函数

_object_remove_assocations函数

结合以上总结的关联对象原理图,就能很好理解_object_get_associative_reference_object_remove_assocations的逻辑。

总结:
关联对象并不是存储在关联对象本身内存中
关联对象存储在全局的一个AssociationsManager中
设置关联对象为nil,就相当于移除了关联对象

相关文章

  • 关联对象-给Category添加属性

    我们知道,分类无法添加成员变量,在分类中定义了属性,系统没有生成对应的成员变量,也没有实现set和get方法。那我...

  • runtime的实际应用

    1、使用关联对象动态给分类添加属性 使用关联对象,可以为类添加Category中的属性,我们可以为现有类添加一些实...

  • Runtime #2

    Category关联属性 为一个对象设置关联对象,实现为其添加属性的效果,使用方法 Method Swizzlin...

  • iOS Runtime面试题(如何给 Category 添加属性

    如何给 Category 添加属性?关联对象以什么形式进行存储? 查看的是 关联对象 的知识点。 详细的说一下 关...

  • iOS - 关联对象

    一、简介 关联对象的使用一般用于给 Category 添加成员变量。我们知道,分类添加属性只能生成 setter/...

  • iOS内存管理-week和关联对象怎么释放(2)

    关联对象可以为category添加成员变量,因为我们虽然可以通过category为类添加属性,但是只是生成了方法声...

  • 2018-05-17. Objective-C中给类别(Cate

    众所周知,类别(Category)是不能直接添加属性的,但是可以利用Runtime来关联对象添加属性。 首先,说一...

  • 我所理解的Runtime:3、对象关联和方法交换

    关联对象 14、使用Category对类进行拓展的时候,只能添加方法,而不适合添加属性(可以添加属性,也可以正常使...

  • OC关联对象小结(一)

    OC关联对象小结(一) 使用场景 为现有的类添加属性,变量 在Objective-C中可以通过Category给一...

  • iOS Runtime面试题(Category)

    Category 有哪些用途? 给系统类添加方法、属性(需要关联对象)。 对某个类大量的方法,可以实现按照不同的名...

网友评论

      本文标题:关联对象-给Category添加属性

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