美文网首页程序员
OC-关联对象AssociatedObject

OC-关联对象AssociatedObject

作者: 东方_未明 | 来源:发表于2018-06-07 10:46 被阅读20次

    关联对象

    前言

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

    1. 生成下划线的成员变量
    2. 生成setter, getter方法的声明
    3. 生成setter, getter方法的实现
    @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
    

    Category中添加属性

    在category中添加属性, 系统只会做一件事情, 生成setter, getter方法的声明.
    我们知道category中不可以添加实例变量, 因为category是一个结构体, 它只可以添加对象/类方法, 协议, 属性

    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;
    };
    
    

    如果要让我们实现可以类似的可以添加实例变量的效果, 那该如何做呢?

    • 方式一: 使用全局字典

      因为我们想让category实现每一个person对象有一个实例变量的效果, 所以我们可以想到在全局创建一个可变字典, 每个person对应一个实例变量, 如下实现:

      NSMutableDictionary *ages_;
      
      @implementation Person (Test1)
      
      + (void)load {
          ages_ = [NSMutableDictionary dictionary];
      }
      
      - (void)setAge:(int)age
      {
          NSString *key = [NSString stringWithFormat:@"%p", self];
          ages_[key] = @(age);
      }
      
      - (int)age {
          NSString *key = [NSString stringWithFormat:@"%p", self];
          return [ages_[key] intValue];
      }
      
      @end
      
      
      1. person对象的实例变量是存储在person对象的内部, 而这种实现方式, 将实例变量存在了全局字典中, 实例变量存储的位置不同
      2. 因为是全局的字典, 所以存在线程安全的问题, 需要在setter方法中加锁
    • 方式二: 使用关联对象

      /**
          object: 需要关联的对象
          key: 指针 类似于字典的key void *
          value: 关联的值
          policy: 内存策略
      */
      objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
      
      objc_getAssociatedObject(self, <#const void * _Nonnull key#>)
      
      

      内存策略:

      objc_AssociationPolicy 对应的修饰符
      OBJC_ASSOCIATION_ASSIGN assign
      OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
      OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
      OBJC_ASSOCIATION_RETAIN strong, atomic
      OBJC_ASSOCIATION_COPY copy, atomic
    • key的定义方式一:

      static const void *ageKey = &ageKey;
      - (void)setAge:(int)age
      {
          objc_setAssociatedObject(self, ageKey, @(age), OBJC_ASSOCIATION_ASSIGN);
      }
      
      

      因为key类似于字典的key, 所以每个关联的值的key是唯一的, 为了唯一性, 我们可以使用: static const void *ageKey = &ageKey; (ageKey这个指针变量存储的是它自己这个变量的地址, 这样写可以保证如果有很多关联的key的话, 可以确保每个key是唯一的)
      static修饰也可以防止其他文件用extern关键字获取这个key
      static 保证这个全局变量只在内部使用

      变量 内存
      ageKey 0x10000 0x10000
      nameKey 0x10008 0x10008
    • key的定义方式二:

      //更加省事而且声明的这个变量只占一个字节 char
      static const char ageKey;
      - (void)setAge:(int)age
      {
          objc_setAssociatedObject(self, &ageKey, @(age), OBJC_ASSOCIATION_ASSIGN);
      }
      
    • key的定义方式三:
      知识点: NSString的内存分配

      // 使用@"age", NSString *str = @"age"; 字面量的字符串变量存储在常量区, 所以@"age", 所以两个方法中的@"age"字符串的内存地址都是一样的. 
      - (void)setAge:(int)age
      {
          objc_setAssociatedObject(self, @"age", @(age), OBJC_ASSOCIATION_ASSIGN);
      }
      
      - (int)age {
          objc_getAssociatedObject(self, @"age")
      }
      
    • key的定义方式四:

      - (void)setAge:(int)age
      {
          objc_setAssociatedObject(self, @selector(age), @(age), OBJC_ASSOCIATION_ASSIGN);
      }
      
      - (int)age {
          // _cmd表示当前方法的@selector, _cmd == @selector(age)
          objc_getAssociatedObject(self, _cmd);
      }
      /*OC的编译器在编译后会在每个方法中加两个隐藏的参数:
      一个是_cmd,当前方法的一个SEL指针。
      一个是self,指向当前对象的一个指针
      (id)self, (SEL)_cmd
      */
      // 当然使用@seletor(setAge:)等其他方法也可以
      

    关联对象的原理

    实现关联对象技术的核心对象有:
    AssociationsManager
    AssociationsHashMap
    ObjectAssociationMap
    ObjcAssociation
    可以通过苹果的开源代码 objc4: objc-references.mm //引用

    class AssociationsManager {
        static AssociationsHashMap *_map;
    }
    
    class AssociationsHashMap: public unordered_map<disguised_ptr_t, ObjectAssociationMap>
    
    class ObjectAssociationMap: public std::map <void *, ObjcAssociation>
    
    class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    }
    

    举例说明:

    @implementation Person (Test)
    
    - (void)setAge:(int)age
    {
        objc_setAssociatedObject(self, @selector(age), @(age), OBJC_ASSOCIATION_ASSIGN);
    }
    
    - (void)setName:(NSString *)name
    {
        objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    @end
    

    上面的代码给Person实例对象关联的两个值(age和name), 底层上是由全局的AssociationsManager管理, AssociationsManager中有一个AssociationsHashMap(字典), 其中以(姑且认为)person为键, AssociationsHashMap(字典)为值. AssociationsHashMap(字典)中以关联值传入的key为键, 以ObjcAssociation对象为值, ObjcAssociation中包含内存策略和value值

    开源代码如下:

    // setter
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
        _object_set_associative_reference(object, (void *)key, value, policy);
    }
    
    void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
        // retain the new value (if any) outside the lock.
        ObjcAssociation old_association(0, nil);
        id new_value = value ? acquireValue(value, policy) : nil;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            // 传入的对象object, 经过DISGUISE(object)函数, 进行内存操作, 作为key
            disguised_ptr_t disguised_object = DISGUISE(object);
            if (new_value) { //如果
                // break any existing association.
                // 根据disguised_object, 找到该对象对应的AssociationsHashMap
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                if (i != associations.end()) {
                    // secondary table exists
                    // 根据i->second找到ObjectAssociationMap的指针
                    ObjectAssociationMap *refs = i->second;
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) {
                        //如果key有对应的`ObjcAssociation`, 则替换
                        old_association = j->second;
                        j->second = ObjcAssociation(policy, new_value);
                    } else {
                        (*refs)[key] = ObjcAssociation(policy, new_value);
                    }
                } else { //如果key没有对应的`ObjcAssociation`, 则创建新的key, ObjcAssociation键值对
                    // create the new association (first time).
                    ObjectAssociationMap *refs = new ObjectAssociationMap;
                    associations[disguised_object] = refs;
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                    object->setHasAssociatedObjects();
                }
            } else {
                // setting the association to nil breaks the association.
                // 如果传入的value为nil值
                AssociationsHashMap::iterator i = associations.find(disguised_object);
                if (i !=  associations.end()) {
                    ObjectAssociationMap *refs = i->second;
                    ObjectAssociationMap::iterator j = refs->find(key);
                    if (j != refs->end()) { //遍历该对象对应的字典中所有的ObjectAssociationMap, 进行抹除操作
                        old_association = j->second;
                        refs->erase(j); // 抹除
                    }
                }
            }
        }
        // release the old value (outside of the lock).
        if (old_association.hasValue()) ReleaseValue()(old_association);
    }
    
    
    // getter
    id objc_getAssociatedObject(id object, const void *key) {
        return _object_get_associative_reference(object, (void *)key);
    }
    
    id _object_get_associative_reference(id object, void *key) {
        id value = nil;
        uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            disguised_ptr_t disguised_object = DISGUISE(object);
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    ObjcAssociation &entry = j->second;
                    value = entry.value();
                    policy = entry.policy();
                    if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                        objc_retain(value);
                    }
                }
            }
        }
        if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
            objc_autorelease(value);
        }
        return value;
    }
    
    
    

    总结:

    1.关联对象并不是存储在被关联对象本身内存中
    2.关联对象存储在全局的统一的一个AssociationsManager
    3.设置关联对象为nil, 就相当于移除关联对象
    4.移除某个对象上的所有的关联对象 void objc_removeAssociatedObjects(id object)
    5.如果某个person对象被销毁了, 则这个person对象所对应的ObjectAssociationMap字典也会被销毁
    6.因为内存策略(objc_AssociationPolicy)中没有weak,

        Person *p = [[Person alloc] init];
            
        {
            Person *tmp = [[Person alloc] init];
            objc_setAssociatedObject(p, @"tmp", tmp, OBJC_ASSOCIATION_ASSIGN);
        }
        NSLog(@"%@", objc_getAssociatedObject(p, @"tmp"));
        // 上面代码会报错误 EXC_BAD_ACCESS, 坏内存地址访问, 因为使用的是OBJC_ASSOCIATION_ASSIGN的内存策略, 出了大括号tmp对象释放
    

    如何设置关联值的时候使用weak策略呢?
    iOS weak 关键字漫谈

    - (void)setContext:(CDDContext*)object {
        id __weak weakObject = object;
        id (^block)() = ^{ return weakObject; };
        objc_setAssociatedObject(self, @selector(context), block, OBJC_ASSOCIATION_COPY);
    }
    
    - (CDDContext*)context {
        id (^block)() = objc_getAssociatedObject(self, @selector(context));
        id curContext = (block ? block() : nil);
        return curContext;
    }
    

    相关文章

      网友评论

        本文标题:OC-关联对象AssociatedObject

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