1. 程式人生 > 實用技巧 >iOS-Category詳解

iOS-Category詳解

Category的底層結構

分類底層結構定義的如下:

//objc-runtime-new.h

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;
    // Fields below this point are not always present on disk.
struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };

專案中每定義一個分類,底層都會增加一個category_t物件。

Category原始碼閱讀順序:

  1. objc-os.mm (runtime入口)
    • _objc_init (runtime初始化)
    • map_images
    • map_images_nolock
  2. objc-runtime-new.mm
    • _read_images
    • remethodizeClass
    • attachCategories
    • attachLists
    • realloc、memmove、 memcpy

category的載入過程:

  1. 通過runtime載入類的所有分類
  2. 將所有分類的方法,屬性,協議分別合併到一個數組
  3. 將合併後的分類資料插入到類原來到資料之前

由原始碼可見,對同名方法而言,會優先呼叫分類中的方法。如果多個分類中包含同名方法,則會呼叫最後參與編譯的分類中的方法。

摘錄原始碼中核心的attachCategories實現如下(objc4-756.2):

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    //方法二維陣列
    //[[method_t,method_t],[method_t,method_t,method_t]]
    //二維陣列中的一個元素(陣列)存放一個分類中的方法列表
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //屬性二維陣列
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //協議二維陣列
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 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();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    //取出(元)類物件中的資料(class_rw_t)
    auto rw = cls->data();
    
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    //將所有分類的方法新增到(元)類物件的方法列表中
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    //將所有分類的屬性新增到(元)類物件的屬性列表中
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    //將所有分類的協議新增到(元)類物件的協議列表中
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

Category和Extension的區別

區別一

  • Category
    • 專門用來給類新增新的方法
    • 不能給類新增成員屬性(其實是可以通過runtime給分類新增屬性)
    • 分類中用@property定義變數,只會生成變數的getter、setter方法的宣告,不能生成方法實現和帶下劃線的成員變數。
  • Extension
    • 可以說是特殊的分類,也稱作匿名分類
    • 可以給類新增成員屬性,但是是私有變數
    • 可以給類新增方法,也是私有方法

區別二

  雖然有人說Extension是一個特殊的Category,也有人將Extension成為匿名分類,但是兩者的區別很大。

  • Category

    • 是執行期決定的
    • 類擴充套件可以新增例項變數,分類不能新增例項變數(原因:因為在執行期,物件的記憶體佈局已經確定,如果新增例項變數會破壞類的內部佈局,這對編譯性語言是災難性的。)
  • Extension

    • 在編譯器決定,是類的一部分,在編譯器和標頭檔案的@interface和實現檔案裡的@implement一起形成了一個完整的類。
    • 伴隨著類的產生而產生,也隨著類的消失而消失。
    • Extension一般用來隱藏類的私有訊息,必須有一個類的原始碼才能新增一個類的Extension,所以對於系統的一個類,比如NSString,就無法新增類擴充套件。

1.Category 基本使用場合

經常用的是給類新增方法,協議、屬性,編寫的分類裡面的方法, 最終是在執行過程中的時候合併到類物件(物件方法)/元類物件(類方法)裡面 (不是編譯的時候合併的)