如何给 Category 添加属性?关联对象以什么形式进行存储?

分类无法添加成员变量,在分类中定义了属性,系统没有生成对应的成员变量,也没有实现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     // 指定相关的对象被复制,原子性   
};
@interface NSObject (Extension)

@property (nonatomic,copy  ) NSString *name;

@end


@implementation NSObject (Extension)

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


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

@end

关联对象原理

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

  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函数源码

接下来查看AssociationManagerAssociationsHashMapObjectAssociationMapObjcAssociation之间的关系。

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

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

unordered_map源码

unordered_map源码中可以看出前两个参数_Key_Tp对应的就是map中的keyvalue,参照传入参数,_Key对应的值就是disguised_ptr_t_Tp对应的值就是ObjectAssociationMap *

接着我们查看ObjectAssociationMap,继承至map,和unordered_map类似,同样以keyvalue方式存储着ObjcAssociation_Key对应着void *_Tp对应着ObjcAssociation

再接着来查看ObjcAssociation

ObjcAssociation源码

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

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

_object_set_associative_reference源码解读

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

关联对象原理图
_object_get_associative_reference函数
_object_get_associative_reference解读
_object_remove_assocations函数
_object_remove_assocations解读

总结

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

iOS面试题汇总

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容