runtime属性の追加

7410 ワード

実行期間クラスオブジェクトにivarを追加することはできません.コンパイル期間クラスのメモリサイズレイアウトが確定しているため、実行期間中にクラスオブジェクトのメモリ空間を変更することはできません.そのため、実行期間にオブジェクトにivarを追加することはできません.例えば、categoryivarを追加することはできません.categoryは実行期間技術ですが、方法を追加することができます.クラスオブジェクト内のメソッドリストはポインタであり、追加メソッド変更はポインタが指すメソッドリストであり、ポインタ自体は変更されず、クラスオブジェクトメモリサイズは変更されないため、categoryメソッドを追加することができる.  category属性を追加するが、この属性に自動的にivarを追加することはなく、setterメソッドとgetterメソッドの宣言のみが生成されることを説明する必要があります.  したがって属性を追加する方法はobjc_setAssociatedObject方式しか使用できないが,以下ではobjc_setAssociatedObjectobjc_getAssociatedObjectの実現原理に重点を置いて述べる.まずobjc_setAssociatedObjectメソッドパラメータの説明を参照してください.
/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */

ここではkeyというパラメータは実はポインタ値で、ポインタ値が異なることを保証すればいいので、ポインタが指すオブジェクト値が等しくても大丈夫で、優雅な書き方があります.
objc_setAssociatedObject(self, _cmd, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
_cmdは現在の方法を表すSELであり、@selector(currentMethod)に相当し、SELは実際にはポインタであり、唯一であることを保証することができる.では、objc_setAssociatedObjectの実装を見てみましょう.
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

直接_object_set_associative_referenceを呼び出して、下を見てください.
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);
}
AssociationsManager managerはc++オブジェクトであり、グローバル唯一の一例hashmapを維持し、構造関数のロックと構造関数のロック解除によってhashmap操作時の安全性を保証する.グローバルhashmapはまず現在のobject(key:DISGUISE(object))に基づいてObjectAssociationMapを検索し、ObjectAssociationMapObjcAssociationオブジェクトであり、実は保存するvaluepolicyの簡単なパッケージである.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; }
    };

まあ、class;
したがって、グローバルhashmapは2次元mapであり、ObjectAssociationMap新しいObjectAssociationMapが作成されていない場合はnew_valueをmapに追加し、ObjectAssociationMapを見つけてパラメータkeyに基づいて古い値を探して、古い値を新しい値に更新することを見つけて、new_を見つけませんでしたvalueをmapに追加します.
検索プロセス全体は2つのレイヤの遍歴です.このコードを理解すると、objc_setAssociatedObject属性の追加は、実際にはシステムがグローバルな2次元mapを維持し、追加する属性とオブジェクトをmapのキー値にバインドしていることがわかります.
_policyの役割は、メソッドでretain valueを開始する必要があるかどうかであり、retain policyであればretainobjc_retain(value)copyであればvalueのcopyメソッド((id(*)(id, SEL))objc_msgSend)(value, SEL_copy)を呼び出す必要がある.そしてメソッドの終了時にRelaseが必要で、次の方法を見てください.
static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

objc_setAssociatedObjectを見終わったら、objc_getAssociatedObjectがどのように実現したのか、あまり言わないでください.
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) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}

最后に1つの小さい问题のコードの中でdisguised_ptr_t disguised_object = DISGUISE(object),disguised_objectはグローバルmapのkey値として、objectはkey値として、disguised_objectはどのように得たのはコードDISGUISE方法の実现を见る必要があります
    inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }


ああ、簡単です.objectはポインタアドレスで、それをuintptr_tに強く変えて(実際にはlongタイプです)、逆取り操作を行います.