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

  不管一個類設計的多麼完美,在將來的需求演進中,都有可能會碰到一些沒法預測的狀況。那怎麼擴展已有的類呢?通常而言,繼承和組合是不錯的選擇。可是在Objective-C 2.0中,又提供了category這個語言特性,能夠動態地爲已有類添加新行爲。現在category已經遍及於Objective-C代碼的各個角落,從Apple官方的framework到各個開源框架,從功能繁複的大型APP到簡單的應用,catagory無處不在。本文對category作了比較全面的整理,但願對讀者有所裨益。  html

  Objective-C中類別特性的做用以下:框架

  (1)能夠將類的實現分散到多個不一樣文件或多個不一樣框架中(補充新的方法)。函數

  (2)能夠建立私有方法的前向引用。佈局

  (3)能夠向對象添加非正式協議。ui

  Objective-C中類別特性的侷限性以下:this

  (1)類別只能想原類中添加新的方法,且只能添加而不能刪除或修改原方法,不能向原類中添加新的屬性。spa

  (2)類別向原類中添加的方法是全局有效的並且優先級最高,若是和原類的方法重名,那麼會無條件覆蓋掉原來的方法。 ssr

 1、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 中

2、爲什麼Category中的方法優先級高於原類中的方法?

  category_t 中的方法列表是插入到主類的方法列表前面(相似利用鏈表中的 next 指針來進行插入),因此這裏 Category 中實現的方法並不會真正的覆蓋掉主類中的方法,只是將 Category 的方法插到方法列表的前面去了。運行時在查找方法的時候是順着方法列表的順序查找的,它只要一找到對應名字的方法,就會中止查找,這裏就會出現覆蓋方法的這種假象了。
// 這裏大概就相似這樣子插入
newproperties->next = cls->data()->properties;
cls->data()->properties = newproperties;,

3、爲什麼Category中不能添加實例變量?

  經過結構體 category_t ,咱們就能夠知道,在 Category 中咱們能夠增長實例方法、類方法、協議、屬性。這裏沒有 objc_ivar_list 結構體,表明咱們不能夠在分類中添加實例變量。
  由於在運行期,對象的內存佈局已經肯定,若是添加實例變量就會破壞類的內部佈局,這個就是 Category 中不能添加實例變量的根本緣由。
 
相關文章
相關標籤/搜索