Objective-C Associated Object

此文實際成于 2015/08/03

Objective-C 給我們提供了為已有類添加屬性的方式。即 Associated Object. 這是 iOS 4 引入的新特性。
為已有的類添加方法,添加屬性,在增強已有類的場景下非常強大好用。但是值得注意的一點就是,
它有時可能會讓運行時混亂,比如添加了沖突的屬性?;蛘咦屖褂谜呋靵y,哪一些是增強的屬性或者方法。
參考: 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 能訪問到到即可。 一種不錯的實現(xiàn)是使用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);
上面存儲關聯(lián)值的最后一個參數(shù),指定了存儲值時一些屬性相關選項。
如下:

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)`

更進一步

下面來看下其代碼的實現(xiàn):

// 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) 具體實現(xiàn)
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);
在上面堆復雜代碼中,真正的執(zhí)行代碼如下 :

    // 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();
        }

大的數(shù)據(jù)結(jié)構(gòu)就是: 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) 釋放老的關聯(lián)值 
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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容