分類(Category)

分類做了哪些事情?

  1. 聲明私有方法
  2. 分解體積龐大的類文件
  3. 把Framework的私有方法公開化
  4. 可以添加實例方法, 類方法, 協(xié)議, 屬性(只添加了get/set方法, 沒有添加實例變量)

分類的特點

  1. 分類運行時決議,在運行時通過runTime把方法等添加到宿主類上. [擴展是編譯時決議, 這是擴展和分類的最大區(qū)別]
  2. 分類可以給系統(tǒng)類添加分類[不能給系統(tǒng)類添加擴展]
  3. 分類可以添加:
  • 實例方法,
  • 類方法,
  • 協(xié)議,
  • 屬性(只添加了get/set方法, 沒有添加實例變量)
  • 可通過關聯(lián)對象添加實例變量

分類的結構體

struct category_t {
    const char *name; 
    classref_t cls;         
    struct method_list_t *instanceMethods; //實例方法
    struct method_list_t *classMethods;  //類方法
    struct protocol_list_t *protocols; //協(xié)議
    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);
};
  • 注意: 結構體中沒有關于實例變量的成員結構

分類加載調(diào)用棧

  • 注意: image在這里表示鏡像, 程序鏡像或者內(nèi)存鏡像
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

/*
我們分析分類單重實例方法的添加邏輯
因此在這里我們假設 isMeta = NO
*/
    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
//獲取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);
    }
}
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;//如果無分類, 直接返回
    if (PrintReplacedMethods) printReplacements(cls, cats);

/*
我們分析分類單重實例方法的添加邏輯
因此在這里我們假設 isMeta = NO
*/
    bool isMeta = cls->isMetaClass();
/*二維數(shù)組
[[method_t, method_t...], [method_t],[method_t, method_t, method_t]]
*/
    // fixme rearrange to remove these intermediate allocations
    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));//協(xié)議列表

    // Count backwards through cats to get newest categories first
    int mcount = 0;//方法參數(shù)
    int propcount = 0;//屬性參數(shù)
    int protocount = 0;//協(xié)議參數(shù)
    int i = cats->count;//分類總數(shù)
    bool fromBundle = NO;

//倒敘遍歷, 后編譯先遍歷
    while (i--) {

//獲取一個分類
        auto& entry = cats->list[i];
//獲取該分類方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
//最后編譯的分類,最先添加到數(shù)組中
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
//屬性列表添加規(guī)則,同方法列表添加規(guī)則
        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;
        }
    }

//獲取宿主類當中rw數(shù)據(jù), 其中包含宿主類的方法列表信息
    auto rw = cls->data();

//主要針對分類中有關內(nèi)存管理相關方法情況下的一些特殊處理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);

/*
rw:代表類
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);
}
/*
addedLists:上一步傳過來二維數(shù)組
[[method_t, method_t...], [method_t],[method_t, method_t, method_t]]
---------A分類方法列表-----  ----B---- ---------C----------
addedCount = 3
*/
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;//列表原有元素總數(shù)
            uint32_t newCount = oldCount + addedCount;//拼接后元素總數(shù)
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));//根據(jù)總數(shù)重新分配內(nèi)存
            array()->count = newCount;//重新設置元素個數(shù)

/*
內(nèi)存移動
[[A], [B],[C],[原來第一個],[原來第二個]]
*/
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));

/*
內(nèi)存拷貝
[[A]-->[addedLists第一個元素]
 [B],-->[addedLists第二個元素]
[C],-->[addedLists第三個元素]
[原來第一個元素],
[原來第二個元素]]
*/
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
1. 分類方法會"覆蓋"宿主類方法原因(其實分類方法和原宿主類方法都存在)

答: 通過memove和memcopy所做操作, 宿主類的方法任然存在, 只不過分類方法在宿主類方法之前, 在消息查找過程中, 根據(jù)選擇器(selector)來查找, 一旦查到就返回, 由于分類方法在前, 故分類方法會優(yōu)先實現(xiàn), 從而會覆蓋宿主類方法

2. 問:多個分類都有一個同名方法, 哪個生效?

答:后編譯的生效

總結:


最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吼渡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子幽邓,更是在濱河造成了極大的恐慌埋哟,老刑警劉巖茅逮,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異幔烛,居然都是意外死亡管行,警方通過查閱死者的電腦和手機本冲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門准脂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人檬洞,你說我怎么就攤上這事狸膏。” “怎么了添怔?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵湾戳,是天一觀的道長。 經(jīng)常有香客問我广料,道長砾脑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任艾杏,我火速辦了婚禮韧衣,結果婚禮上,老公的妹妹穿的比我還像新娘购桑。我一直安慰自己畅铭,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布勃蜘。 她就那樣靜靜地躺著硕噩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缭贡。 梳的紋絲不亂的頭發(fā)上炉擅,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音阳惹,去河邊找鬼谍失。 笑死,一個胖子當著我的面吹牛莹汤,可吹牛的內(nèi)容都是我干的袱贮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼体啰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗽仪?” 一聲冷哼從身側響起荒勇,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闻坚,沒想到半個月后沽翔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年仅偎,在試婚紗的時候發(fā)現(xiàn)自己被綠了跨蟹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡橘沥,死狀恐怖窗轩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情座咆,我是刑警寧澤痢艺,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站介陶,受9級特大地震影響堤舒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哺呜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一舌缤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧某残,春花似錦国撵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至椭豫,卻和暖如春耻瑟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赏酥。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工喳整, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裸扶。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓框都,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呵晨。 傳聞我的和親對象是個殘疾皇子魏保,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容