天天看點

【Objective-C】探索Category底層的實質

  無論一個類設計的多麼完美,在未來的需求演進中,都有可能會碰到一些無法預測的情況。那怎麼擴充已有的類呢?一般而言,繼承群組合是不錯的選擇。但是在Objective-C 2.0中,又提供了category這個語言特性,可以動态地為已有類添加新行為。如今category已經遍布于Objective-C代碼的各個角落,從Apple官方的framework到各個開源架構,從功能繁複的大型APP到簡單的應用,catagory無處不在。本文對category做了比較全面的整理,希望對讀者有所裨益。  

  Objective-C中類别特性的作用如下:

  (1)可以将類的實作分散到多個不同檔案或多個不同架構中(補充新的方法)。

  (2)可以建立私有方法的前向引用。

  (3)可以向對象添加非正式協定。

  Objective-C中類别特性的局限性如下:

  (1)類别隻能想原類中添加新的方法,且隻能添加而不能删除或修改原方法,不能向原類中添加新的屬性。

  (2)類别向原類中添加的方法是全局有效的而且優先級最高,如果和原類的方法重名,那麼會無條件覆寫掉原來的方法。 

 一、Category的底層實作

   Objective-C 通過 Runtime 運作時來實作動态語言這個特性,所有的類和對象,在 Runtime 中都是用結構體來表示的,Category 在 Runtime 中是用結構體 category_t 來表示的,下面是結構體 category_t 具體表示:

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;      

  從category的定義也可以看出category的可為(可以添加執行個體方法,類方法,甚至可以實作協定,添加屬性)和不可為(無法添加執行個體變量)。

  我們将結合 runtime 的源碼探究下 Category 的實作原理。打開 runtime 源碼工程,在檔案 objc-runtime-new.mm 中找到以下函數:

void _read_images(header_info **hList, uint32_t hCount)
{
    ...
        _free_internal(resolvedFutureClasses);
    }    // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);            if (!cls) {                // Category's target class is missing (probably weak-linked).                // Disavow any knowledge of this category.
                catlist[i] = nil;                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }                continue;
            }            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            BOOL classExists = NO;            if (cat->instanceMethods ||  cat->protocols                ||  cat->instanceProperties)
            {
                addUnattachedCategoryForClass(cat, cls, hi);                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s",
                                 cls->nameForLogging(), cat->name,
                                 classExists ? "on existing class" : "");
                }
            }            if (cat->classMethods  ||  cat->protocols                /* ||  cat->classProperties */)
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }    // Category discovery MUST BE LAST to avoid potential races 
    // when other threads call the new category code before 
    // this thread finishes its fixups.    // +load handled by prepare_load_methods()
    ...
}      

  我們可以知道在這個函數中對 Category 做了如下處理:

  (1)将 Category 和它的主類(或元類)注冊到哈希表中;

  (2)如果主類(或元類)已實作,那麼重建它的方法清單;

  Category的實作原理:

  • 在編譯時期,會将分類中實作的方法生成一個結構體 method_list_t 、将聲明的屬性生成一個結構體 property_list_t ,然後通過這些結構體生成一個結構體 category_t 。
  • 然後将結構體 category_t 儲存下來
  • 在運作時期,Runtime 會拿到編譯時期我們儲存下來的結構體 category_t
  • 然後将結構體 category_t 中的執行個體方法清單、協定清單、屬性清單添加到主類中
  • 将結構體 category_t 中的類方法清單、協定清單添加到主類的 metaClass 中

二、為何Category中的方法優先級高于原類中的方法?

  category_t 中的方法清單是插入到主類的方法清單前面(類似利用連結清單中的 next 指針來進行插入),是以這裡 Category 中實作的方法并不會真正的覆寫掉主類中的方法,隻是将 Category 的方法插到方法清單的前面去了。運作時在查找方法的時候是順着方法清單的順序查找的,它隻要一找到對應名字的方法,就會停止查找,這裡就會出現覆寫方法的這種假象了。

// 這裡大概就類似這樣子插入newproperties->next = cls->data()->properties;
cls->data()->properties = newproperties;,      

三、為何Category中不能添加執行個體變量?