Objective-C Associated Object

9643 ワード

この文書は実際に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 %@
",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の運転時、
  • グローバルなobject->O b jectAssociationMapのAssociationsHashMapという名前のHashテーブルを維持します.
  • 、ObjectAssociationMapは、あるオブジェクトに属するKey対ValueのHashマッピングテーブルである.
  • は、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