Objective-C Associated Object

此文实际成于 2015/08/03

Objective-C 给我们提供了为已有类添加属性的方式。即 Associated Object. 这是 iOS 4 引入的新特性。
为已有的类添加方法,添加属性,在增强已有类的场景下非常强大好用。但是值得注意的一点就是,
它有时可能会让运行时混乱,比如添加了冲突的属性。或者让使用者混乱,哪一些是增强的属性或者方法。
参考: http://nshipster.com/associated-objects/

为 NSObject 类添加 author 属性。

代码如下:

@interface NSObject (MyObject)
@property (nonatomic,strong) NSString * author;
@end

@implementation NSObject (MyObject)

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

- (void )setAuthor:(NSString*)author{
    objc_setAssociatedObject(self, "author", author, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

使用的时候像平常一样即可:

        NSObject * myObject = [[NSObject alloc] init];
        myObject.author = @"banxi";
        
        NSLog(@"myObject author is %@\n",myObject.author);

objc_getAssociatedObject(self, "author"); 这样的取值方式,让人觉得每一个 Objective-C 的对象都自带了
一个 Key-Value 容器。上面容器是 self, key 是"author". 不过这里 Key 不必是字符串。
因为其类型声明是 const void *key 只要setter 与 getter 一致即可。
一般推荐的做法是声明 key 为static char 类型,或者某一个指针。基本上满足是常量,唯一,并且其 setter,getter 能访问到到即可。 一种不错的实现是使用selector

Since SELs are guaranteed to be unique and constant, you can use _cmd as the key for objc_setAssociatedObject(). #objective-c #snowleopard

— Bill Bumgarner (@bbum) August 28, 2009

Associative Object Behaviors

objc_setAssociatedObject(self, "author", author, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
上面存储关联值的最后一个参数,指定了存储值时一些属性相关选项。
如下:

enum {
    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. */
};

这些策略选项,与@property声明的等价声明如下:

ASSIGN           ->  `@property (assign) or @property (unsafe_unretained)`
RETAIN_NONATOMIC ->  `@property (nonatomic,strong)`
COPY_NONATOMIC   ->  `@property (nonatomic,copy)`
RETAIN           ->   `@property (atomic, strong)`
COPY             ->   `@property (atomic,copy)`

更进一步

下面来看下其代码的实现:

// 1) 声明
void 
objc_setAssociatedObject(id object, const void *key, id value, 
                         objc_AssociationPolicy policy) 
{
    objc_setAssociatedObject_non_gc(object, key, value, policy);
}

//  2)特定方法
void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

//  3) 具体实现
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());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    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);
                object->setHasAssociatedObjects();
            }
        } 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);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

针对我们上面的代码 objc_setAssociatedObject(self, "author", author, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
在上面堆复杂代码中,真正的执行代码如下 :

    // 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());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value){
                 // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
        }

大的数据结构就是: Objective-C 的运行时,

  1. 维护一张全局的 从 object -> ObjectAssociationMap 名为 AssociationsHashMap 的 Hash 表。
  2. 而 ObjectAssociationMap 是一张属于某一个对象的 从 Key 对 Value 的 Hash 映射表。
  3. 为也记录 Value 的存储的 引用处理策略,使用 ObjcAssociation 来包装 Value.
    class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    }

保存中指定的 Policy 会在 存储值或者取值时使用,主要有以下使用:

// 1)保存时
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;
}
// 2) 释放老的关联值 
static void releaseValue(id value, uintptr_t policy) {
    if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
    }
}
// 3) 从 Hash 表中取出值时
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);

// 4)同上
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }

Policy 的位标志声明:

// expanded policy bits.

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 {
    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. */
};

其二进制表示:

policy               
COPY                 1403  10101111011
RETAIN               1401  10101111001
COPY_NONATOMIC          3  00000000011
RETAIN_NONATOMIC        1  00000000001
ASSIGN                  0  00000000000
---------------------------------------
SETTER_ASSIGN           0  00000000000
SETTER_RETAIN           1  00000000001
SETTER_COPY             3  00000000011
GETTER_READ      (0 << 8)  00000000000
GETTER_RETAIN    (1 << 8)  00100000000
GETTER_AUTORELEASE(2 << 8) 01000000000

你可能感兴趣的:(Objective-C Associated Object)