分類與拓展

分類(Category)

Category是Objective-C 2.0之後添加的語言特性,分類、類別其實都是指的Category。Category的主要作用是爲已經存在的類添加方法。

我們用分類都做了什麼事

  • 聲明私有方法(我們定義一個分類,只把頭文件放在對應宿主文件的.m當中,滿足私有方法的聲明和使用,對外不暴露)
  • 分解體積龐大的類文件(比如一個類的功能特別複雜,那麼我們可以按照功能來對類當中的方法進行分類,把同一個功能的方法放在一個分類文件當中)
  • 把Framewrok的私有方法公開化

特點

  • 運行時決議(我們在編寫好分類的文件後,它並沒有把分類的內容添加到宿主類上面,這個時候宿主類中還沒有分類中的方法,而是在運行時通過runtime把分類中添加的內容真實的添加到宿主類上面)
  • 可以爲系統類添加分類(比如我們經常會用到獲取UIView座標的分類方法,就是在對UIView添加了分類)

分類中可以添加哪些內容

  • 實例方法
  • 類方法
  • 協議
  • 屬性(其實只是聲明瞭get、set方法,並沒有添加實例變量,可以通過關聯對象來添加實例變量)

我們來看看分類的底層結構

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, struct header_info *hi);
};

我們通過分類的結構就可以更好的印證我們能夠爲分類添加哪些內容,這裏並沒有實例變量的成員結構.

源碼分析

objc-runtime-new.mm
這裏主要分析實例方法的添加邏輯.
我們從 remethodizeClass 這個方法開始分析

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
//判斷當前類是否是元類對象,也就是判斷添加的方法是實例方法還是類方法
    isMeta = cls->isMetaClass();
    // 獲取cls中未完成整合的所有分類
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
      //將cats拼接到cls上
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

來看看attachCategories這個拼接分類到宿主類的方法

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;//如果當前類沒有分類的話直接返回
    if (PrintReplacedMethods) printReplacements(cls, cats);
    //判斷當前類是否是元類對象
    bool isMeta = cls->isMetaClass();

    /*
二維數組 [[method_t,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));//協議列表

    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;
        }
    }
    // 獲取宿主類當中的rw數據,其中包含宿主類的方法列表信息
    auto rw = cls->data();
    // 主要是針對 分類中有關於內存管理相關方法情況下的一些特殊處理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    /*
    methods代表類的方法列表,attachLists方法表示將mcount個元素的mlists(方法列表)拼接到rw(宿主類)的methods(方法列表)上
    */
    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);
}

演示圖例

添加分類的流程
  • 獲取分類列表的count,然後原來的類方法列表內存移動count
  • 分類列表內存拷貝到原來的類方法列表的前方
  • 同樣的方法,優先調用分類的方法
  • 分類具有同樣的方法,根據編譯順序決定,取最後編譯分類的方法列表

拓展(Extension)

我們用拓展都做了什麼事

  • 聲明私有屬性
  • 聲明私有方法
  • 聲明私有成員變量

拓展的特定

  • 編譯時決議
  • 只以聲明存在,沒有具體實現,寄生於宿主類.m中
  • 不能爲系統類添加擴展

分類、擴展區別

  • 分類中原則上只能增加方法(能添加屬性的的原因只是通過runtime解決無setter/getter的問題而已)
  • 類擴展不僅可以增加方法,還可以增加實例變量(或者屬性),只是該實例變量默認是@private類型的(用範圍只能在自身類,而不是子類或其他地方)
  • 類擴展中聲明的方法沒被實現,編譯器會報警,但是類別中的方法沒被實現編譯器是不會有任何警告的。這是因爲類擴展是在編譯階段被添加到類中,而類別是在運行時添加到類中。
  • 類擴展不能像分類那樣擁有獨立的實現部分(@implementation部分),也就是說,類擴展所聲明的方法必須依託對應類的實現部分來實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章