OC Category、AssociatedObject

韋家冰發表於2017-12-13

Category原理、作用 深入理解Objective-C:Category (OC1.0) 結合 category 工作原理分析 OC2.0 中的 runtime

Objective-C Associated Objects 的實現原理 關聯物件 AssociatedObject 完全解析

結構基礎:

struct category_t {
    const char *name;                               // category小括號裡寫的名字
    classref_t cls;                                 // cls要擴充套件的類物件,編譯期間這個值是不會有的,在app被runtime載入時才會根據name對應到類物件
    struct method_list_t *instanceMethods;          // 例項方法
    struct method_list_t *classMethods;             // 類方法
    struct protocol_list_t *protocols;              // category實現的protocol,比較不常用在category裡面實現協議,但是確實支援的
    struct property_list_t *instanceProperties;     // category所有的property,這也是category裡面可以定義屬性的原因,不過這個property不會@synthesize例項變數,一般有需求新增例項變數屬性時會採用objc_setAssociatedObject和objc_getAssociatedObject方法繫結方法繫結,不過這種方法生成的與一個普通的例項變數完全是兩碼事。
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;       // 類屬性(@property (class,nonatomic, strong) NSString *name; 然後還需要現實+的getter setter方法,然後就可以用.號代用

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
複製程式碼

Category載入的函式呼叫棧

最後的方法: 對 Category 中方法的解析並不複雜,首先來看一下 attachCategories 的簡化版程式碼(只有methods,properties與protocols的類似):

static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
    if (!cats) return;
    bool isMeta = cls->isMetaClass();

    method_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));
    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int i = cats->count;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
}
複製程式碼

1、首先,通過 while 迴圈,我們遍歷所有的 category,也就是引數 cats 中的 list 屬性。對於每一個 category,得到它的方法列表 mlist 並存入 mlists 中。 2、最後:rw->methods.attachLists(mlists, mcount); 把mlists放在前面,主類方法放在後面。

######總結: 1、Categroy的實現,執行時先把Category中的方法遍歷出來mlists,rw-> methods.attachLists(mlists, mcount),把mlists放在前面,主類方法放在後面。

2、在主類和Category中,同名方法可以同時存在,且不不是覆蓋關係。執行時,當呼叫到同名方法,被呼叫的是在方法列表中排在前面的Category方法。

3、Category中,同名方法不是覆蓋關係,因此還是可以想辦法呼叫主類方法的。其途徑就是獲取當前物件的方法列表,從列表中找出最後一個同名方法,就是主類的方法。

4、在主類的+load方法中可以呼叫Category的方法,因為Category的載入是在load方法之前就完成的。對於主類和Category中都有+load的方法的時候,順序是先主類後Category。

5、associatedObject儲存變數是儲存在一個全域性的HashMap中,物件的地址是Key,value是另一個HashMap,這個HashMap裡儲存Key-Value對。

另外:

1、對系統提供的類做分類,分類是在執行時附加到相關類上的 2、對自己建立的類做分類,分類是在編譯期附加到相關類上的 Runtime原始碼 —— 關於category的一個問題

####2.關聯屬性的具體實現 基本用法

Category.h
@property(nonatomic,copy) NSString *name;
複製程式碼
Category.m
- (void)setName:(NSString *)name
{
    // 各個引數簡單解析
    // 1、self--關聯的源物件
    // 2、@selector(name)--關聯的key
    // 3、name--與物件的鍵鍵關聯的值。可以通過nil來清除現有的關聯。
    // 4、OBJC_ASSOCIATION_COPY--關聯值的記憶體管理策略,有如下選擇:
        /*
         OBJC_ASSOCIATION_ASSIGN             --@property(assign) / @property(unsafe_unretained)
         OBJC_ASSOCIATION_RETAIN_NONATOMIC   --@property(nonatomic, strong)
         OBJC_ASSOCIATION_COPY_NONATOMIC     --@property(nonatomic, copy)
         OBJC_ASSOCIATION_RETAIN             --@property(atomic, strong)
         OBJC_ASSOCIATION_COPY               --@property(atomic, copy)
         */
    objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    return objc_getAssociatedObject(self, @selector(name));
}
複製程式碼

系統原始碼

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) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    // 根據傳進來的value與policy,acquireValue其實只是retain或者copy
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // AssociationsManager管理者
        AssociationsManager manager;
        // 獲取AssociationsHashMap
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) { //
            // 獲取關聯的源物件self對應的ObjectAssociationMap
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) { 
  //  ObjectAssociationMap *refs就是self對應的ObjectAssociationMap
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    // 查詢key對應ObjcAssociation存在,更新new_value
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 查詢key對應ObjcAssociation不存在,設定新的ObjcAssociation進去
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else { // ObjectAssociationMap不存在
                // 設定新的ObjectAssociationMap、設定新的ObjcAssociation 儲存到associations
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                // 設定物件的has_assoc為true,標記這個物件有關聯屬性(物件釋放時候用到這個標記)
                object->setHasAssociatedObjects();
            }
        } else { // 傳入的value是nil,就是移除這個key的關聯
            
            // 獲取關聯的源物件self對應的ObjectAssociationMap
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                // 查詢key對應ObjcAssociation
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    // 持有這個ObjcAssociation,最後釋放物件用
                    old_association = j->second;
                    // ObjectAssociationMap中移除ObjcAssociation
                    refs->erase(j);
                }
            }
        }
    }
    // 如果原來的關聯物件有值的話,會呼叫 ReleaseValue() 釋放關聯物件的值
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

複製程式碼

在看這段程式碼前,我們需要先了解一下幾個資料結構以及它們之間的關係:

AssociationsManager:是頂級的物件,維護了一個從 spinlock_t 鎖到 AssociationsHashMap 雜湊表的單例鍵值對對映; AssociationsHashMap :是一個無序的雜湊表,(物件地址做key :ObjectAssociationMap); ObjectAssociationMap:是一個 C++ 中的 map ,(關聯 key : ObjcAssociation); ObjcAssociation: 是一個 C++ 的類,表示一個具體的關聯結構,主要包括兩個例項變數,_policy 表示關聯策略,_value 表示關聯物件。 每一個物件地址對應一個 ObjectAssociationMap 物件,而一個 ObjectAssociationMap 物件儲存著這個物件的若干個關聯記錄。

AssociationsManager管理圖

objc_setAssociatedObject流程圖.png

objc_getAssociatedObject

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
複製程式碼
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
                ObjcAssociation &entry = j->second;
                // 取出value
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    // 這裡判斷get的記憶體策略(暫時不知道get的記憶體策略怎麼使用,可能是系統私有使用的)
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}
複製程式碼

objc_removeAssociatedObjects

void objc_removeAssociatedObjects(id object) 
{
    // 判斷物件、判斷是否已經標記為關聯物件
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
複製程式碼

_object_remove_assocations只是在Map裡面for 釋放關聯物件的值

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > 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());
}
複製程式碼

AssociatedObject 本身並不支援新增具備 weak 特性的 property, 但我們可以通過一個小技巧來完成:新增了一箇中間角色 block,再輔以 weak 關鍵字就實現了具備 weak 屬性的 associated object。

- (void)setContext:(NSObject*)object {
    id __weak weakObject = object;
    id (^block)() = ^{ return weakObject; };
    objc_setAssociatedObject(self, @selector(context), block, OBJC_ASSOCIATION_COPY);
}

- (NSObject*)context {
    id (^block)() = objc_getAssociatedObject(self, @selector(context));
    id curContext = (block ? block() : nil);
    return curContext;
}
複製程式碼

相關文章