美文网首页
iOS-浅谈OC中的关联对象

iOS-浅谈OC中的关联对象

作者: 晴天ccc | 来源:发表于2019-05-30 14:15 被阅读0次

目录

  • 前言
  • 工作中遇到问题的思考
    ---- 直接在分类添加成员变量
    ---- 在分类中增加属性
  • 如何在分类中添加成员变量?
    ----【方法一】利用字典给分类添加成员变量
    ----【方法二】利用关联对象给分类添加成员变量
  • 关联对象简介
  • 关联对象作用
  • 基本用法
  • key的用法
  • 关联对象的实现原理
  • 总结

前言

在ARC环境下,在一个类中声明一个属性@property (nonatomic, assign) int age;那系统类似的帮我们生成如下代码:

@interface Person : NSObject
{
    int _age;
}

- (void)setAge:(int)age;
- (int)age;

@end

@implementation Person

- (void)setAge:(int)age{
    _age = age;
}
- (int)age{
    return _age;
}
@end

下划线的成员变量,setter, getter方法的声明和实现。

工作中遇到问题的思考

在平时的工作中经常碰到给类别添加属性的操作,那么实现思路是怎么样的呢?

  • 直接在分类添加成员变量

@interface Person (walk) {
    int _weight;
}

系统报错:!Instance variables may not be placed in categories(实例变量不能放在类别中)

说明分类中不能直接添加成员变量!!!
  • 在分类中增加属性

@interface Person (walk)
@property (nonatomic, assign) int weight;
@end

当调用set或者get方法时,控制台会报错:

-[Person setWeight:]: unrecognized selector sent to instance 0x1007160e0
-[Person weight]: unrecognized selector sent to instance 0x105011920

说明分类可以添加属性,但是添加属性后,只会自动添加set和get方法声明,不会实现set和get方法

本质原因:通过底层我们可以看到分类的结构体类型_category_t,里面并没有存储成员变量。

struct _category_t {
    const char *name;                               // 类名
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;  //对象方法列表
    const struct _method_list_t *class_methods;     //类方法列表
    const struct _protocol_list_t *protocols;       //协议列表
    const struct _prop_list_t *properties;          //属性列表
};

如何在分类中添加成员变量?

  • 【方法一】利用字典给分类添加成员变量

// 声明一个全局字典,用于保存属性
NSMutableDictionary *dic_;

+ (void)load {
    dic_ = [[NSMutableDictionary alloc] init];
}

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

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

通过分类中的字典可以保存分类里添加属性的值,但是有以下几个缺点

  • 内存泄漏,字典是全局变量,会一直保存在内存中。
  • 线程安全,不同的对象在不同的线程同时访问分类中的成员变量,可能会导致读写错误。
  • 每次给这个添加一个新的属性时需要重新创建一个新的字典保存。
  • 代码量比较大,实现起来比较麻烦。
  • 【方法二】利用关联对象给分类添加成员变量

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

- (NSString *)name{
    // 隐式参数
    // _cmd == @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

关联对象简介

C是一种动态语言,它的方法,对象的类型都是到运行的时候才能够确定的。所以这就使得OC存在了关联对象这一强大的机制。
所谓关联对象,其实就是我们在运行时对一个已存在的对象上面绑定一个对象,使两个对象变成动态的聚合关系。

关联对象作用

关联对象一般用于动态的扩展一个对象,但是这一般都是在其他方法不行的事后才会使用,因为关联对象很可能会出现难以查找的Bug。

  • 为现有的类添加属性,变量。
    在Objective-C中可以通过Category给一个现有的类添加属性(如NSObject),但是却不能添加实例变量,然而可以通过Associated Object间接地达到这一目的。
  • 为KVO创建一个关联的观察者。

基本用法

  • 添加关联对象,传nil可以移除相关的关联对象。
objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy);
  • 获取关联对象
objc_getAssociatedObject(id object, const void * key);
  • 移除所有关联对象,如果打算只移除一部分则不能使用该方法。
objc_removeAssociatedObjects(id _Nonnull object);
  • 使用关联对象时会用到objc_AssociationPolicy
  • 它其实和我们OC中的属性修饰符一一对应,使用什么修饰符,取决于你定义的属性的类型,对应关系如下:
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     // 指定相关的对象被复制,原子性   
};

代码举例:

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @"key", name, OBJC_ASSOCIATION_COPY);
}

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

我们看到这里都需要一个关键值:key,下面就对这个key进行探究。

key的用法

  • 使用变量地址作为key

static const void *NameKey = &NameKey;

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY);
}

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

缺点:声明一个全局指针变量,占用8个字节,且书写麻烦。

  • 使用char类型变量地址作为key

static const char NameKey;

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

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

优点:char类型只占用1个字节
缺点:书写麻烦

  • 使用字符串作为key

#define NameKey @"name"

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY);
}

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

缺点:书写麻烦

  • 使用方法地址作为key(推荐)

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

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

优点:可读性较高、如果方法名写错系统会有提示,书写简单。
其中_cmd == @selector(name),OC方法有两个隐式参数,一个是self,一个是_cmd.
比如name函数可能是:

- (NSString *)nameSelf:(id)self cmd:(SEL):_cmd {
}

关联对象的实现原理

查看 【objc源码
objc-references.mm中的_object_set_associative_reference方法和_object_get_associative_reference方法:

_object_get_associative_reference(id object, const void *key)
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){}

可以看出关联对象过程中主要用到一下几个数据结构:
AssociationsManager、AssociationsHashMap、ObjectAssociationMap、ObjcAssociation

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

class AssociationsManager {
......    
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
......
};

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
......    
}

它们的结构关系如下:

运行时通过map维系一张关联对象与被关联对象之间的关系。

  • 所有实例对象的关联对象都由一个AssociationsManager管理。
  • 其内部有一个AssociationsHashMap对象,其内部保存着实例对象(object)和存储着实例对象对应自己关联对象的ObjectAssociationMap
  • ObjectAssociationMap内部存储着关联对象的key和对应关联对象的value和策略。

总结

  • 1、关联对象并不是存储在被关联对象本身。
  • 2、关联对象存储在全局统一的一个AssociationsManager中。
  • 3、移除关联对象将关联对象设置nil即可(通过查看源码可知,当value==nil时会调用erase方法)。
  • 4、移除所有关联对象,调用objc_removeAssociatedObjects(id _Nonnull object)。
  • 5、如果被关联对象销毁,对应的关联属性也都会销毁。

相关文章

  • iOS-浅谈OC中的关联对象

    目录 前言工作中遇到问题的思考---- 直接在分类添加成员变量---- 在分类中增加属性如何在分类中添加成员变量?...

  • iOS-浅谈OC对象

    Objective-C的本质 我们平时编写的Objective-C代码底层是由C\C++实现的。OC的面向对象是基...

  • iOS-浅谈OC中的Class对象

    目录 Class对象----class的结构----class_rw_t的结构----class_ro_t的结构结...

  • iOS-浅谈OC中对象的类型

    目录 OC对象的类型Instance对象(实例对象)----实例(instance)对象内存结构Class对象(类...

  • Swift 为分类增加属性objc_getAssociated

    OC 获取关联对象 Swift 获取关联对象——错误的写法 Swift 获取关联对象——正确的写法 设置关联对象 ...

  • iOS-浅谈OC对象的本质

    目录 Objective-C的本质NSObject的底层实现一个NSObject对象的底层内存分布情况内存占用查看...

  • OC关联对象

    ,❓思考一下:分类中能否添加属性?能否添加成员变量?直接上代码验证一下:创建一个Person类和它的分类Perso...

  • OC 关联对象

    关联对象是指某个OC对象通过一个唯一的key连接到一个类的实例上. 举个栗子: mikey是Person类的一个实...

  • OC关联对象

    分类实现原理、加载时机[https://www.jianshu.com/p/7c830ce3c303] 一. 为什...

  • OC知识点整理-(零)内容概要

    我们在OC中基础中,尝尝会被问到下面几个基础方面的问题。 OC基础 Category 关联对象 代理,通知 KVO...

网友评论

      本文标题:iOS-浅谈OC中的关联对象

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