1. 程式人生 > IOS開發 >Runtime原始碼解讀6(分類)

Runtime原始碼解讀6(分類)

2019-10-15

本文介紹分類(Category)的實現以及分類的載入過程。分類是對 Objective-C 類的一種擴充套件方式。說到分類不可不提擴充套件(Extension)。擴充套件通常被視為匿名的分類,但是兩者實現的區別還是很大的:

  • 擴充套件只是對介面的擴充套件,所有實現還是在類的@implementation塊中,分類是對介面以及實現的擴充套件,分類的實現在@implementation(CategoryName)塊中;
  • 分類在 Runtime 中有category_t結構體與之對應,而擴充套件則沒有;
  • 擴充套件是編譯時決議,分類是執行時決議,分類在執行載入階段才載入方法列表中;

注意:分類是裝飾器模式。用分類擴充套件的好處是:對父類的擴充套件可以直接作用於其所有的衍生類。

一、資料結構

1.1 分類的定義

分類的資料結構是category_t結構體。包含了分類名稱name,分類所擴充套件的類cls,分類實現的例項方法列表instanceMethods,分類實現的類方法列表classMethods,分類遵循的協議列表protocols,分類定義的屬性列表instanceProperties

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;

    method_list_t *methodsForMeta(bool isMeta) {
        if
(isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta) { if (isMeta) return nil; // classProperties; else return instanceProperties; } }; 複製程式碼

1.2 分類列表

類並不包含像分類列表這樣的資料結構,category_t結構體只是為了在編譯階段記錄開發者定義的分類,並將其儲存到特定的容器中。但是程式本身則需要儲存分類列表,因為載入程式時,需要按照容器內記錄的分類資訊依次載入分類。儲存應用定義的所有分類的容器是category_list

,也是locstamped_category_list_t的別名。locstamped_category_list_t是順序表容器,元素為locstamped_category_t結構體。locstamped_category_t結構體包含指向category_t結構體的cat成員。

// 分類列表category_list是locstamped_category_list_t的別名
typedef locstamped_category_list_t category_list;

// 分類列表中的元素的型別,包含指向分類的指標
struct locstamped_category_t {
    category_t *cat;
    struct header_info *hi;  // 先忽略
};

// locstamped_category_list_t 陣列容器是實現了分類列表
struct locstamped_category_list_t {
    uint32_t count;
#if __LP64__
    uint32_t reserved;
#endif
    locstamped_category_t list[0];
};
複製程式碼

二、分類載入

分類的載入是在應用的載入階段,當應用完成類的基本資訊(編譯時決議的資訊)載入後,需要將類的所有分類中的定義屬性列表、方法列表等元素也新增到類的class_rw_t中。

分類載入的核心程式碼在_read_images(...)邏輯中。其中的關鍵環節包括:1、呼叫addUnattachedCategoryForClass(...)將分類新增到類的 未處理分類列表;2、呼叫remethodizeClass(...)根據分類重構類的方法列表,兩個處理成對存在。前者收集所有未載入的分類列表,後者將未載入的分類列表中的方法列表、屬性列表等資訊載入到類中。

void _read_images(header_info **hList,uint32_t hCount)
{
    #define EACH_HEADER \
    hIndex = 0;         \
    crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \
    hIndex++

    ...

    // 載入分類
    for (EACH_HEADER) {
        // 獲取分類列表,分類列表的順序是後編譯的分類在前(由attachCategories中分類列表的遍歷順序,
        // 結合attachList的行為特徵 以及 類的不同分類定義的同名方法的呼叫優先順序,共同推斷出)
        category_t **catlist = 
            _getObjc2CategoryList(hi,&count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // 類為空則丟棄該分類的所有資訊
                catlist[i] = nil;
                continue;
            }

            // 首先將分類新增到類的未處理分類
            // 然後處理逐個類的未處理分類,重構類的屬性列表、方法列表
            // 類的處理
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat,cls,hi);
                if (cls->isRealized()) {
                    // 必須保證 class realizing 已完成,因為`class_ro_t`資訊固定後
                    // 才能開始配置`class_rw_t`中的資訊
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            // 元類的處理
            if (cat->classMethods  ||  cat->protocols  
                /* ||  cat->classProperties */) 
            {
                addUnattachedCategoryForClass(cat,cls->ISA(),hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");  

    ...

}
複製程式碼

2.1 addUnattachedCategoryForClass

尚未將方法列表、屬性列表等資訊新增到所擴充套件類的class_rw_t中 的分類,統一儲存在一個靜態的NXMapTable類的雜湊表中。該雜湊表以Class作為關鍵字,category_list為值。通過unattachedCategories()獲取該雜湊表。(category_list *)NXMapGet(unattachedCategories(),cls)表示獲取類cls的未處理分類列表,需要對cls新增未處理分類時,將其新增到類的未處理分類列表的結尾。

static void addUnattachedCategoryForClass(category_t *cat,Class cls,header_info *catHeader)
{
    runtimeLock.assertWriting();

    // 不可用cat->cls替代,因為cls可能是cat->cls->isa(操作類方法時)
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats,cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]),1);
    } else {
        list = (category_list *)
            realloc(list,sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }

    // 將分類新增到list的末尾
    list->list[list->count++] = (locstamped_category_t){cat,catHeader};
    NXMapInsert(cats,list);
}

static NXMapTable *unattachedCategories(void)
{
    runtimeLock.assertWriting();

    static NXMapTable *category_map = nil;

    if (category_map) return category_map;

    category_map = NXCreateMapTable(NXPtrValueMapPrototype,16);

    return category_map;
}
複製程式碼

2.2 remethodizeClass

remethodizeClass用於處理 2.1 中生成的unattachedCategories雜湊表中的所有分類 ,將分類擴充套件的方法、屬性、協議,逐一新增到類的方法列表、屬性列表、協議列表中。類的方法列表二維陣列容器中後編譯的分類的method_list_t在前,先編譯的分類的method_list_t在後。在方法列表中查詢方法時是以從開頭到結尾的順序遍歷找到第一個匹配的方法返回。因此,響應方法時,後編譯的分類的方法的優先順序,高於先編譯的分類的同名方法

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    if ((cats = unattachedCategoriesForClass(cls,false/*not realizing*/))) {
        attachCategories(cls,cats,true /*flush caches*/);        
        free(cats);
    }
}

// 將類的未處理分類列表從雜湊表中推出
static category_list *
unattachedCategoriesForClass(Class cls,bool realizing)
{
    runtimeLock.assertWriting();
    return (category_list *)NXMapRemove(unattachedCategories(),cls);
}

static void 
attachCategories(Class cls,category_list *cats,bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls,cats);

    bool isMeta = cls->isMetaClass();

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

    // 注意分類列表的遍歷是從後向前的,因此方法列表二維陣列容器中,元素對應的分類的順序
    // 和cats是一致的。
    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);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    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_t結構體儲存,將所有分類資訊收集到unattachedCategories雜湊表中,用於記錄 尚未將方法列表、屬性列表等資訊新增到所擴充套件類的class_rw_t中 的分類。雜湊表以分類所擴充套件的類(Class)為關鍵字,擴充套件了該類的所有分類組成的分類列表為值,分類列表的資料結構是category_list結構體;

  • 完成收集unattachedCategories雜湊表後,將unattachedCategories中所有分類的方法列表、屬性列表、協議列表新增到 所擴充套件類的class_rw_t中;

  • 後編譯的分類的方法的優先順序,高於先編譯的分類的同名方法。即在類的方法列表二維陣列容器中,後編譯的分類的method_list_t在前,先編譯的分類的method_list_t在後,類的基礎方法列表在最末尾;

  • 下一篇介紹 runtime 實現面向物件的一些具體實現細節;