從源碼解讀Category實現原理

什麼是category?

category是 Objective-C 2.0 以後添加的語言特性,主要做用是爲已經存在的類添加方法。除此以外,Apple 還推薦了category 的另外兩個使用場景。安全

  1. 能夠把類的實現分開在幾個不一樣的文件裏面。這樣作有幾個顯而易見的好處
    • 能夠減小單個文件的體積
    • 能夠把不一樣的功能組織到不一樣的 category 裏
    • 能夠由多個開發者共同完成一個類
    • 能夠按需加載想要的 category 等等
  2. 聲明私有方法

不過除了apple推薦的使用場景,還衍生出了 category 的其餘幾個使用場景:bash

  • 模擬多繼承
  • 把framework的私有方法公開

category特色

  • category 只能給某個已有的類擴充方法,不能擴充成員變量
  • category 中也能夠添加屬性,只不過 @property 只會生成 settergetter 的聲明,不會生成 settergetter 的實現以及成員變量
  • 若是 category 中的方法和類中原有方法同名,category 中的方法會覆蓋掉類中原有的方法
  • 若是多個 category 中存在同名的方法,運行時到底調用哪一個方法由編譯器決定,後面參與編譯的方法會覆蓋前面同名的方法,因此最後一個參與編譯的方法會被調用

這裏說的是覆蓋而不是替換,是由於後編譯的方法被放在了方法列表的前面而已,runtime機制先找到前面的方法來執行數據結構

Category VS Extension

category 經常拿來與 extension 作比較,extension 同樣能夠添加屬性和方法,extension 看起來很像一個匿名的 category。但實際上二者幾乎徹底是兩個東西app

  • extension 運行在編譯期,它就是類的一部分,拓展的方法,屬性和變量一塊兒造成一個完整的類。category 是運行期決議的,此時對象的內存佈局已經肯定,沒法再添加實例變量
  • extension 通常用來隱藏類的私有信息,你必須有一個類的源碼才能爲一個類添加 extension
  • extension 和 category 均可以添加屬性,可是 category 的屬性不能生成成員變量和 getter、setter 方法的實現

category原理

講了一堆category的做用和特色,咱們來看一下category的定義ide

typedef struct category_t *Category;

struct category_t {
    const char *name;	//category名稱
    classref_t cls; 	//要拓展的類
    struct method_list_t *instanceMethods; //給類添加的實例方法的列表
    struct method_list_t *classMethods;  //給類添加的類方法的列表
    struct protocol_list_t *protocols;  //給類添加的協議的列表
    struct property_list_t *instanceProperties;  //給類添加的屬性的列表
};

複製代碼

實際上 Category 是一個 category_t 的結構體,裏面維護着類的信息和category的名稱,以及類方法列表,實例方法列表,協議的列表和屬性的列表函數

那麼Category是怎麼加載的呢?佈局

咱們知道,Objective-C 的運行是依賴 OC 的 runtime 的, 而 OC 的 runtime 和其餘系統庫同樣,是OS X和iOS經過dyld動態加載的ui

咱們從OC運行時,入口方法出發atom

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼

到真正完成綁定 category 的函數attachCategories中間的函數調用棧是spa

void _objc_init(void);
└── void map_images(...);
    └── void map_images_nolock(...);
        └── void _read_images(...);
            └── void _read_images(...);
                └── static void remethodizeClass(Class cls);
                    └──attachCategories(Class cls, category_list *cats, bool flush_caches);
複製代碼

咱們來看一下 attachCategories 源碼的簡易版:

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));

    int mcount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

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

    auto rw = cls->data();

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

從上面的代碼能夠看出,增長方法的操做實際是分配一個大的實例方法列表 method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists)); 再經過 for 循環將category中的方法列表填入這個大的列表,最後交給 rw->methods.attachLists(mlists, mcount);將方法列表增長到類的方法列表上去。其餘的屬性添加與此相似

咱們再看一段 attachLists的源碼

void attachLists(List* const * addedLists, uint32_t addedCount) {
 if (hasArray()) {
        
        //舊的方法列表的長度
        uint32_t oldCount = array()->count;
        
        //新的方法列表的長度
        uint32_t newCount = oldCount + addedCount;
        
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        
        //從addedCount的偏移量添加舊的方法列表
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        //從開始添加新方法列表
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
        }
}
複製代碼

從上面的代碼也驗證了咱們上面所說的,同名的方法是覆蓋而不是替換,category的方法被放到了新方法列表的前面

category和關聯對象

如上所述,category 的屬性不能生成成員變量和 gettersetter 方法的實現,咱們要本身實現 gettersetter 方法,需藉助關聯對象來實現

關聯對象來實現提供三個接口 objc_setAssociatedObject,objc_getAssociatedObject,objc_removeAssociatedObjects,他們分別調用的是

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

void objc_removeAssociatedObjects(id object) {
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
複製代碼

他們調用的接口都位於 objc-references.mm文件中,

_object_get_associative_reference
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;
}
複製代碼

這段代碼引用的類型有

  • AssociationsManager
  • AssociationsHashMap
  • ObjcAssociationMap
  • ObjcAssociation

AssociationsManager源碼

spinlock_t AssociationsManagerLock;

class AssociationsManager {
    static AssociationsHashMap *_map;
public:
    // 初始化時候
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    // 析構的時候
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    // associations 方法用於取得一個全局的 AssociationsHashMap 單例
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};
複製代碼

AssociationsManager 初始化一個 AssociationsHashMap 的單例,用自旋鎖 AssociationsManagerLock 保證線程安全

AssociationsHashMap源碼

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    
複製代碼

AssociationsHashMap 是一個map類型,用於保存對象的對象的 disguised_ptr_tObjectAssociationMap 的映射

ObjectAssociationMap源碼

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
複製代碼

ObjectAssociationMap 則保存了從 key 到關聯對象 ObjcAssociation 的映射,這個數據結構保存了當前對象對應的全部關聯對象

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; }
    };

複製代碼

ObjcAssociation 就是真正的關聯對象的類,上面的全部數據結構只是爲了更好的存儲它。

最關鍵的 ObjcAssociation 包含了 policy 以及 value

用一張圖解釋他們的關係就是:

從上圖咱們不難看出 _object_get_associative_reference 獲取關聯對象的步驟是:

  1. AssociationsHashMap &associations(manager.associations()) 獲取 AssociationsHashMap 的單例對象 associations
  2. disguised_ptr_t disguised_object = DISGUISE(object) 獲取對象的地址
  3. 經過對象的地址在 associations 中獲取 AssociationsHashMap迭代器
  4. 經過 key獲取到 ObjectAssociationMap的迭代器
  5. 最後得出關聯對象類 ObjcAssociation 的實例 entry,再獲取到 valuepolicy 的值
_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.
    uintptr_t old_policy = 0; // NOTE:  old_policy is always assigned to when old_value is non-nil.
    id new_value = value ? acquireValue(value, policy) : nil, old_value = nil; // 調用 acquireValue 對 value 進行 retain 或者 copy
    {

        // & 取地址 *是指針,就是地址的內容
        AssociationsManager manager;  // 初始化一個 AssociationsManager 類型的變量 manager
        AssociationsHashMap &associations(manager.associations());   // 取得一個全局的 AssociationsHashMap 單例
        if (new_value) {

            // 若是new_value不爲空,開始遍歷associations指向的map,查找object對象是否存在保存聯合存儲數據的ObjectAssociationMap對象

            // 查找map中是否包含某個關鍵字條目,用 find() 方法,傳入的參數是要查找的key(被關聯對象的內存地址),在這裏須要提到的是begin()和end()兩個成員,分別表明map對象中第一個條目和最後一個條目,這兩個數據的類型是iterator.
            // 定義一個條目變量 i (實際是指針)
            AssociationsHashMap::iterator i = associations.find(object);  // AssociationsHashMap 是一個無序的哈希表,維護了從對象地址到 ObjectAssociationMap 的映射;


            // iterator是 C++ 中的迭代器 , 這句話是定義一個 AssociationsHashMap::iterator 類型的變量 i,初始化爲 associations.find(object) , associations是AssociationsHashMap類型對象。

            // 經過map對象的方法獲取的iterator數據類型 是一個std::pair對象
            // 根據對象地址獲取起對應的 ObjectAssociationMap對象
            if (i != associations.end()) {
                // 存在

                // object對象在associations指向的map中存在一個ObjectAssociationMap對象refs

                // ObjectAssociationMap 是一個 C++ 中的 map ,維護了從 key(就是外界傳入的key) 到 ObjcAssociation 的映射,即關聯記錄
                ObjectAssociationMap *refs = i->second;              //  指針 調用方法 須要用 ->   i 是 AssociationsHashMap    i->second 表示ObjectAssociationMap  i->first 表示對象的地址
                ObjectAssociationMap::iterator j = refs->find(key);  //  根據傳入的關聯對象的key(一個地址)獲取其對應的關聯對象  ObjectAssociationMap


                // 關聯對象是否存在
                if (j != refs->end()) {
                    // 使用過該key保存value,用新的value和policy替換掉原來的值
                    // 若是存在 持有舊的關聯對象
                    ObjcAssociation &old_entry = j->second;  
                    old_policy = old_entry.policy;
                    old_value = old_entry.value;

                    // 存入新的關聯對象
                    old_entry.policy = policy;
                    old_entry.value = new_value;
                } else {
                    // 沒用使用過該key保存value,將value和policy保存到key映射的map中
                    // 若是不存在 直接存入新的關聯對象
                    (*refs)[key] = ObjcAssociation(policy, new_value);   // 對map 插入元素
                }
            }
            else {

                // 不存在
                // 沒有object就建立
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                _class_setInstancesHaveAssociatedObjects(_object_getClass(object));
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    ObjcAssociation &old_entry = j->second;
                    old_policy = old_entry.policy;
                    old_value = (id) old_entry.value;

                    // 從 map中刪除該項
                    refs->erase(j);
                }
            }
        }
    }

    // 舊的關聯對象是否存在,若是存在,釋放舊的關聯對象。
    // release the old value (outside of the lock).
    if (old_value) releaseValue(old_value, old_policy);
}
複製代碼

_object_set_associative_reference設置關聯對象的流程參照圖片:

關聯策略

在給一個對象添加關聯對象時有五種關聯策略可供選擇:

關聯策略 等價屬性 說明
OBJC_ASSOCIATION_ASSIGN @property (assign) or @property (unsafe_unretained) 弱引用關聯對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (strong, nonatomic) 強引用關聯對象,且爲非原子操做
OBJC_ASSOCIATION_COPY_NONATOMIC @property (copy, nonatomic) 複製關聯對象,且爲非原子操做
OBJC_ASSOCIATION_RETAIN @property (strong, atomic) 強引用關聯對象,且爲原子操做
OBJC_ASSOCIATION_COPY @property (copy, atomic) 複製關聯對象,且爲原子操做
_object_remove_assocations
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()) {
            // 獲取到全部的關聯對象的associations實例
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            delete refs;    //刪除ObjectAssociationMap
            associations.erase(i);//刪除AssociationsHashMap
        }
    }
    //刪除elements集合中的全部ObjcAssociation元素
    for_each(elements.begin(), elements.end(), ReleaseValue());
}
複製代碼

刪除關聯對象的流程相對就比較簡單了,將獲取到的關聯對象ObjcAssociation的實例放入一個 vector中,刪除對應的 ObjectAssociationMapAssociationsHashMap,最後對 vector 中每一個 ObjcAssociation 實例作release操做

總結

Category在iOS開發中是比較常見的,用於給現有的類拓展新的方法和屬性。本文從底層分析了Category的原理,以及關聯對象實現,使你們對Category能有一個更深的認識,在之後的開發工做中能更好的使用這一特性。

相關文章
相關標籤/搜索