OC关联对象及其内存管理

前言

OC的关联对象经常被用来存储Category中的属性对应的实例变量,当然它还有其他的用途。

这篇文章将讨论关联对象是如何实现的,以及关联对象的内存管理问题。

Demo

先用一段代码来引入要讲的东西:

#import "HCBase.h"

@interface HCBase (vanney)

@property (nonatomic, assign) int cui;
@property (nonatomic, strong) NSString *van;

@end

/* -----------------  这里是分割线  -------------- */

#import "HCBase+vanney.h"

@implementation HCBase (vanney)

- (void)setCui:(int)cui {
    objc_setAssociatedObject(self, "cui", @(cui), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)cui {
    return [objc_getAssociatedObject(self, "cui") intValue];
}

- (void)setVan:(NSString *)van {
    objc_setAssociatedObject(self, "van", van, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

@end

可以看到:

  1. 在头文件中,我们定义了两个属性 cuivan
  2. 在.m文件中,我们使用了关联对象来存储这两个实例变量
  3. 使用关联对象时,我们还指定了它的内存管理的方式 OBJC_ASSOCIATION_RETAIN_NONATOMICOBJC_ASSOCIATION_COPY_NONATOMIC

关联对象的实现

关联对象的实现是开源的,在runtime的objc-references.m源文件中。在OC中,使用一个全局的表来存储所有的关联对象,如下图所示:

association

关于这张图的解释可以参考我的这篇文章Objective-C runtime - 分类与关联对象

上面的Demo使用了下面两个函数来存取关联对象,我们来看看他们的源码

  1. objc_setAssociatedObject
  2. objc_getAssociatedObject

objc_setAssociatedObject

这个方法用来设置关联对象:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // 关联对象使用 ObjcAssociation 这个c++对象来存储。
      // 设置新对象,那么有可能有老的对象会被废弃。那么新建一个ObjcAssociation对象,之后用来存储老的对象
    ObjcAssociation old_association(0, nil);

      // acquireValue函数: 设置新的对象的retainCount,也就是对他进行手动内存管理
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);

          // 如果确实是要设置新关联对象的时候
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);

              // 这个对象之前设置过关联对象,也就是说该对象的关联对象数量大于1
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);

                  // 寻找全局的关联对象表,如果存在老对象的话
                if (j != refs->end()) {
                      // 存储老对象到之前新建的ObjcAssociation对象中
                    old_association = j->second;
                      // 设置新的关联对象
                    j->second = ObjcAssociation(policy, new_value);
                }
                  // 不存在老对象,直接设置新关联对象
                  else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            }

              // 这个对象之前没有设置过任何的关联对象
              else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);

                  // 将对象的isa的has_assoc字段设置成true
                object->setHasAssociatedObjects();
            }
        } 
          // 要将关联对象设成 nil,也就是取消该关联对象。先存老对象,再在全局表中删除该关联对象
          else {
            // setting the association to nil breaks the association.
            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()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }


    // 释放老对象,retainCount相关,内存管理相关
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

可以看到设置新的关联对象其实很简单:

  1. 现在全局关联对象表中寻找这个key
  2. 先存原来关联对象
  3. 再设置新的关联对象
  4. 释放原有的关联对象

objc_getAssociatedObject

获取关联对象就更简单了,直接到表中去找对应的key的对象就好了。

关联对象的内存管理

在上面讲到的源代码中,涉及到管理对象内存管理的有两个函数

  1. acquireValue
  2. ReleaseValue

requireValue

static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

可以看到,设置关联对象的内存管理方式和你手动传进来的policy有关。

将policy和0XFF相与:

  1. 结果是OBJC_ASSOCIATION_SETTER_RETAIN的话,就调用对象的retain函数,也就是将retainCount加一
  2. 结果是OBJC_ASSOCIATION_SETTER_COPY的话,就调用对象的copy函数,也就是将拷贝对象,并给新对象的retainCount赋1

PS :

  1. 没有找到具体的SEL_copy的实现。
  2. 至于retain的实现,其实就是将对象的isa的extra_rc字段加1 具体可以参考这篇博客OC源码 —— retain和release

各种常量

在我们自己调用存取关联对象的时候使用了objc_AssociationPolicy常量:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

根据名字很容易想到所对应的内存管理语义。

objc-references.mm中使用的是另外的常量:

enum { 
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0,
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,            // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8), 
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8), 
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};

可以看到这两个enum中,前三个是相对应的,他们有相同的值

ReleaseValue

static void releaseValue(id value, uintptr_t policy) {
    if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
    }
}

struct ReleaseValue {
    void operator() (ObjcAssociation &association) {
        releaseValue(association.value(), association.policy());
    }
};

再来看看如何释放对象。可以看到,他是将关联对象的policyOBJC_ASSOCIATION_SETTER_RETAIN相与,简单计算就会发现:当policy为retain和copy时,这个if判断都是正确的,也就会对相应关联对象执行release操作。和我们预想的是一致的。

参考

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器