分類-Category

weixin_33807284發表於2017-02-23

1 簡介

  • 主要為類新增方法:不管系統的類還是自定義的類
  • 宣告私有方法(擴充套件)
  • 分類是執行期決議的;擴充套件是編譯期決議,需要原始碼才能在主類新增擴充套件;所以系統類是無法新增擴充套件
  • 分類的結構體,沒有成員變數(ivar)列表,所以無法新增成員變數
typedef struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
} category_t;

2 Category如何載入

  • 把Category的例項方法,協議以及屬性新增到主類
  • 把Category的類方法和協議新增到類的metaClass

新增方法的策略:
是將Category和主類的方法拼接起來,放到方法列表飯(陣列):先載入主類的方法,載入分類方法時,是先把分類方法放到新陣列,再把主類的方法放到新陣列後
而查詢方法也是按照順序查詢,所以會有Category覆蓋主類方法的說法

for (uint32_t m = 0;
             (scanForCustomRR || scanForCustomAWZ)  &&  m < mlist->count;
             m++)
        {
            SEL sel = method_list_nth(mlist, m)->name;
            if (scanForCustomRR  &&  isRRSelector(sel)) {
                cls->setHasCustomRR();
                scanForCustomRR = false;
            } else if (scanForCustomAWZ  &&  isAWZSelector(sel)) {
                cls->setHasCustomAWZ();
                scanForCustomAWZ = false;
            }
        }

        // Fill method list array
        newLists[newCount++] = mlist;
    .
    .
    .

    // Copy old methods to the method list array
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }

3 +load的載入順序

先呼叫主類的load方法,再呼叫Category;Category之間的順序由編譯順序決定

4 關聯物件(AssociatedObject)

#import "MyClass.h"

@interface MyClass (Category1)

@property(nonatomic,copy) NSString *name;

@end

#import "MyClass+Category1.h"
#import <objc/runtime.h>

@implementation MyClass (Category1)

+ (void)load
{
    NSLog(@"%@",@"load in Category1");
}

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,
                             "name",
                             name,
                             OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;
}

@end

所有的關聯物件都由AssociationsManager管理,通過Hash表管理,key是物件地址,value是Hash表,這個Hash表則儲存了所有的KV對。AssociationsManager定義如下:

class AssociationsManager {
    static OSSpinLock _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { OSSpinLockLock(&_lock); }
    ~AssociationsManager()  { OSSpinLockUnlock(&_lock); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager裡面是由一個靜態AssociationsHashMap來儲存所有的關聯物件的。這相當於把所有物件的關聯物件都存在一個全域性map裡面。而map的的key是這個物件的指標地址(任意兩個不同物件的指標地址一定是不同的),而這個map的value又是另外一個AssociationsHashMap,裡面儲存了關聯物件的kv對。而在物件的銷燬邏輯裡面,見objc-runtime-new.mm:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa_gen = _object_getClass(obj);
        class_t *isa = newcls(isa_gen);

        // Read all of the flags at once for performance.
        bool cxx = hasCxxStructors(isa);
        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);

        if (!UseGC) objc_clear_deallocating(obj);
    }

    return obj;
}

嗯,runtime的銷燬物件函式objc_destructInstance裡面會判斷這個物件有沒有關聯物件,如果有,會呼叫_object_remove_assocations做關聯物件的清理工作。

參考文章

http://tech.meituan.com/DiveIntoCategory.html

相關文章