Runtimeソース——Associated Object

12456 ワード

これはすでに前の文章で何度も言及したが、ずっと深く入り込んでいないので、この文章は研究に来た.
runtimeが提供するassociated objectに関するインタフェースは3つあります.
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object) ;

1つ目を切り込み点として選び、詳しく分析し、他の2つの方法を少し話します.
objc_setAssociatedObject
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) {
    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) {
            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;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            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);
                }
            }
        }
    }
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

associated objectのアクセスプロセスを理解するには、この方法で説明したいくつかのクラスについて、階層順に十分な理解が必要です.
  • AssociationsManager
  • // class AssociationsManager manages a lock / hash table singleton pair.
    // Allocating an instance acquires the lock, and calling its assocations() method
    // lazily allocates it.
    class AssociationsManager {
        static spinlock_t _lock;
        static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
    public:
        AssociationsManager()   { _lock.lock(); }
        ~AssociationsManager()  { _lock.unlock(); }
        
        AssociationsHashMap &associations() {
            if (_map == NULL)
                _map = new AssociationsHashMap();
            return *_map;
        }
    };
    

    注記書きましたが、AssociationsManagerは、ハッシュ・テーブルにスピン・ロックされた単一のマッピングを管理します.管理されているAssociationsHashMapの一例は、associations()メソッドによって取得できます.
  • AssociationsHashMap
  • class AssociationsHashMap : public unordered_map
     {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    

    これは、オブジェクトアドレス(setメソッドの最初のパラメータの反転)からObjectAssociationMapへのマッピングを格納する無秩序ハッシュテーブルです.
  • ObjectAssociationMap
  • class ObjectAssociationMap : public std::map
     {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    

    この表には、関連付けられたオブジェクトObjcAssociationへのkey(setメソッドの2番目のパラメータ)のマッピングが格納されています.
  • ObjcAssociation
  • class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    public:
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        ObjcAssociation() : _policy(0), _value(nil) {}
    
        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };
    

    関連オブジェクトの情報を保存します.valueは3番目のパラメータに対応し、policyは4番目のパラメータに対応します.
    これでsetメソッドの4つのパラメータが使用され、setメソッドを分解するのは簡単です.
  • ObjcAssociation old_association(0, nil);
    

    まず、1つのオブジェクトが存在する可能性のある古いvalueを格納するために使用されることを宣言します.すなわち、1つのオブジェクトに対して2回のsetメソッドを呼び出した場合、2回目のsetのときに、1つ目のsetに入ったvalueを解放する必要があります.このオブジェクトは、前回のvalueを解放するために格納します.
  • id new_value = value ? acquireValue(value, policy) : nil;
    
    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;
    }
    
    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,    
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
        OBJC_ASSOCIATION_RETAIN = 01401,      
        OBJC_ASSOCIATION_COPY = 01403          
    };
    

    setメソッドを呼び出すpolicyに基づいて対応するメソッドを呼び出す.例えばretainやcopy.
  • AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
    disguised_ptr_t disguised_object = DISGUISE(object);
    
    inline disguised_ptr_t DISGUISE(id value) { return ~ uintptr_t(value); }
    typedef uintptr_t disguised_ptr_t;
    

    まずAssociationsManagerを取得し、コンストラクション関数でロックをかけます.次にassociations()メソッドでAssociationsHashMapの単一例を取得し、最初の2つのステップが慣れていないように見える場合は、書き直してください.
    AssociationsManager manager = AssociationsManager();
    AssociationsHashMap &associations = manager.associations();
    

    最後の行はobjectのアドレスを逆にし、後にAssociationsHashMapのキーとして使用されます.
  • if (new_value) {
        AssociationsHashMap::iterator i = associations.find(disguised_object);
    (1)  if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
    (2)     if (j != refs->end()) {
                old_association = j->second;
                j->second = ObjcAssociation(policy, new_value);
            } else {
                (*refs)[key] = ObjcAssociation(policy, new_value);
            }
        } else {
            ObjectAssociationMap *refs = new ObjectAssociationMap;
            associations[disguised_object] = refs;
            (*refs)[key] = ObjcAssociation(policy, new_value);
            object->setHasAssociatedObjects();
        }
    }
    

    新しいvalueが入力されたかどうかを判断し、新しいvalueが入力された場合、関連オブジェクトを設定するプロセスに入ります.
    まず、前のステップで処理したオブジェクトアドレスを検索します.
  • (1)==true AssociationsHashMapの定義に従って、ObjectAssociationMapを直接取得します.
  • (2)==trueは、関連オブジェクトを設定したことを表し、元の値をold_に保存します.associationでは,後のリリースとして残し,新しい値をObjcAssociation(policy,new_value)コンストラクション関数で格納する.
  • (2)==falseはkeyによって関連オブジェクトが見つからず,keyのvalueとして新しいObjcAssociationオブジェクトを直接構築する.
  • (1)==falseは、オブジェクトアドレスによって検索されず、関連オブジェクトが設定されていないことを表す.最終パス:
  • object->setHasAssociatedObjects()
    

    isaのhas_をassocフィールドをtrueに設定します.
    ソースコードを読むにつれて、最初のRuntimeソースコード--オブジェクト、クラス、isaのisaに慣れていないフィールドも一つずつ見られました.融通が利くような感じがして、ははは.
  • 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);
            }
        }
    }
    

    setメソッドを呼び出して新しい値が入力されない場合は、関連オブジェクトをObjectAssociationMapから削除します.オブジェクトをnilにしてreleaseするようにします.
  • // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
    
    struct ReleaseValue {
        void operator() (ObjcAssociation &association) {
            releaseValue(association.value(), association.policy());
        }
    };
    
    static void releaseValue(id value, uintptr_t policy) {
        if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
            ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
        }
    }
    

    最後のステップは、元の値を解放します.もちろんpolicyがretainのものだけが解放され、assignやcopyのものは必要ありません.
    これがsetメソッドのすべてのプロセスです.2層mapがネストされているので、少し回りそうに見えます.
    objc_getAssociatedObject
    setのプロセスを理解し、getメソッドのパラメータを通過します.
    id _object_get_associative_reference(id object, void *key)
    

    getのプロセスを推測すると、次のようになります.
  • まずAssociationsManagerの一例を取得し、さらにAssociationsHashMap
  • を取得する.
  • object取得ObjectAssociationMap
  • keyによるObjcAssociation
  • の取得
  • ObjcAssociationのvalueを取り出し、
  • に戻る.
    合理的に見えますが、ソースコードを見てみましょう.
    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) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
                }
            }
        }
        if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
            ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
        }
        return value;
    }
    

    予測の流れとほぼ同じですが、ObjcAssociationからvalueを取得する場合、policyに従って対応するretain,autoreleaseを行う必要があります.
    objc_removeAssociatedObjects
    この方法は、オブジェクトのすべての関連オブジェクトを削除するために使用されます.パラメータは1つです.クリーンアップするオブジェクトです.
    void objc_removeAssociatedObjects(id object) 
    {
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object);
        }
    }
    
    void _object_remove_assocations(id object) {
        vector< ObjcAssociation,ObjcAllocator > elements;
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.associations());
            if (associations.size() == 0) return;
            disguised_ptr_t disguised_object = DISGUISE(object);
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // copy all of the associations that need to be removed.
                ObjectAssociationMap *refs = i->second;
                for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                    elements.push_back(j->second);
                }
                // remove the secondary table.
                delete refs;
                associations.erase(i);
            }
        }
        // the calls to releaseValue() happen outside of the lock.
        for_each(elements.begin(), elements.end(), ReleaseValue());
    }
    

    関連オブジェクトが存在するかどうかを判断し、存在する場合はクリアする必要があります.
    パージのプロセスは、次のセクションに分けられます.
  • は、ObjectAssociationMapに存在する全てのObjcAssociation
  • を記憶する.
  • ObjectAssociationMapメモリを解放し、AssociationsHashMapから
  • を削除します.
  • すべてのObjcAssociationを解放し、最後にそのReleaseValue()メソッドは、必要なオブジェクトに対してrelease
  • を呼び出すことである.
    すべてが完了すると、AssociationsHashMapにはこのオブジェクトの関連オブジェクトは存在しません.
    まとめ
  • 関連付けられたオブジェクトは、関連付けられたオブジェクトとデータ構造上はあまり関係なく、関連付けられたオブジェクトはAssociationsManagerによって管理する
  • である.
  • 通常objc_をアクティブに呼び出す必要はありませんremoveAssociatedObjects(...)メソッドでは、関連オブジェクトを削除すると、オブジェクトrelease->deallocのときに自動的に呼び出され、関連オブジェクトを削除する場合はobjc_が呼び出されます.setAssociatedObject(...)メソッドを選択し、valueをnilに設定します.
  • categoryで関連オブジェクトによって実装されるget/setインタフェースは、属性が属性のように見えるだけで、category自体はインスタンス変数を追加できません.