當(dāng)別人問Category為什么不能添加屬性

學(xué)習(xí)寫簡書博客,每次寫簡書都是對過往的回顧

前言

這段時(shí)間換工作引瀑,發(fā)現(xiàn)面試經(jīng)常會問到一個(gè)問題:
分類中能不能定義實(shí)例變量驼鹅,為什么?
答案:不能纽门。類的內(nèi)存布局在編譯時(shí)期就已經(jīng)確定了薛耻,category是運(yùn)行時(shí)才加載的早已經(jīng)確定了內(nèi)存布局所以無法添加實(shí)例變量,如果添加實(shí)例變量就會破壞category的內(nèi)部布局赏陵。
繼續(xù)追問:
1:為什么說category是在運(yùn)行時(shí)加載的饼齿?
2:不能添加實(shí)例變量,那為什么能添加屬性蝙搔?
參考了一些大神的說法之后缕溉,最終在runtime源碼里找到了答案。

先來看看在runtime中的結(jié)構(gòu)體的樣子

在分類轉(zhuǎn)化為c++文件中可以看出_category_t結(jié)構(gòu)體中吃型,存放著類名证鸥,對象方法列表,類方法列表勤晚,協(xié)議列表枉层,以及屬性列表。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CategoryName.m
image.png

1:category小括號里寫的名字
2:要擴(kuò)展的類對象赐写,編譯期間這個(gè)值是不會有的鸟蜡,在app被runtime加載時(shí)才會根據(jù)name對應(yīng)到類對象
3:這個(gè)category所有的-方法
4:這個(gè)category所有的+方法
5:這個(gè)category實(shí)現(xiàn)的protocol,比較不常用在category里面實(shí)現(xiàn)協(xié)議挺邀,但是確實(shí)支持的
6:這個(gè)category所有的property矩欠,這也是category里面可以定義屬性的原因,不過這個(gè)property不會@synthesize實(shí)例變量悠夯,一般有需求添加實(shí)例變量屬性時(shí)會采用objc_setAssociatedObject和objc_getAssociatedObject方法綁定方法綁定癌淮,不過這種方法生成的與一個(gè)普通的實(shí)例變量完全是兩碼事。
這里已經(jīng)可以回答第二個(gè)問題了沦补。

再看catagory如何添加進(jìn)runtime
runtime源碼下載鏈接:

鏈接: https://pan.baidu.com/s/11GLMCKU64trxksQaK9ZBvg 提取碼: xu23

其實(shí)在main函數(shù)之前乳蓄,將runtime通過dyld動態(tài)加載進(jìn)來的時(shí)候生效的。怎么驗(yàn)證夕膀,再來看runtime源碼:
先從objc_init開始虚倒,其中大量出現(xiàn)的image并不是圖片,而是一個(gè)二進(jìn)制文件(可執(zhí)行文件或 so 文件)产舞,里面是被編譯過的符號魂奥、代碼等,所以 ImageLoader 作用是將這些文件加載進(jìn)內(nèi)存易猫,且每一個(gè)文件對應(yīng)一個(gè)ImageLoader實(shí)例來負(fù)責(zé)加載耻煤。

void _objc_init(void)
{
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    lock_init();
    exception_init();
        
    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}

在load_images之后調(diào)用_read_images方法初始化map后的image,這里面干了很多的事情,像load所有的類哈蝇、協(xié)議和category棺妓。
再仔細(xì)看category的初始化:

// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            class_t *cls = remapClass(cat->cls);
            // 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 (isRealized(cls)) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            if (cat->classMethods  ||  cat->protocols 
                /* ||  cat->classProperties */)
            {
                addUnattachedCategoryForClass(cat, cls->isa, hi);
                if (isRealized(cls->isa)) {
                    remethodizeClass(cls->isa);
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 getName(cls), cat->name);
                }
            }
        }
    }

__objc_catlist,就是上面category存放的數(shù)據(jù)段炮赦。
以上代碼做的事:
1把category的實(shí)例方法怜跑、協(xié)議以及屬性添加到類上。
2把category的類方法和協(xié)議添加到類的metaclass上吠勘。
具體怎么做性芬,主要是兩個(gè)方法addUnattachedCategoryForClass和remethodizeClass。
addUnattachedCategoryForClass實(shí)現(xiàn)映射剧防,remethodizeClass去做具體操作批旺。
再往下看category的各種列表是怎么最終添加到類上的。
點(diǎn)開attachCategoryMethods方法可以看到它將所有category的實(shí)例方法列表拼成了一個(gè)大的實(shí)例方法列表诵姜,再通過attachMethodLists去加到方法列表里

static void 
attachCategoryMethods(class_t *cls, category_list *cats,
                      BOOL *inoutVtablesAffected)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    BOOL isMeta = isMetaClass(cls);
    method_list_t **mlists = (method_list_t **)
        _malloc_internal(cats->count * sizeof(*mlists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int i = cats->count;
    BOOL fromBundle = NO;
    while (i--) {
        method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= cats->list[i].fromBundle;
        }
    }

    attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);

    _free_internal(mlists);

}

結(jié)論:
1)category的方法并沒有“完全替換掉”原來類已經(jīng)有的方法,而是把擴(kuò)展的方法放入到方法列表的前頭搏熄,舉個(gè)栗子(原來的方法列表<a,b,c,>棚唆,擴(kuò)展的方法是<1,2,3>,會變成<1,2,3,a,b,c>心例。)
2)為什么平常所說的category的方法會“覆蓋”掉原來類的同名方法宵凌,就是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的,而且只要一找到對應(yīng)名字的方法止后,就會結(jié)束查找瞎惫。

另外的一些疑問

問:Category中有l(wèi)oad方法嗎?load方法是什么時(shí)候調(diào)用的译株?load 方法能繼承嗎瓜喇?
答:Category中有l(wèi)oad方法,load方法在程序啟動裝載類信息的時(shí)候就會調(diào)用歉糜。load方法可以繼承乘寒。調(diào)用子類的load方法之前,會先調(diào)用父類的load方法

問:load匪补、initialize的區(qū)別伞辛,以及它們在category重寫的時(shí)候的調(diào)用的次序。
答:區(qū)別在于調(diào)用方式和調(diào)用時(shí)刻
調(diào)用方式:load是根據(jù)函數(shù)地址直接調(diào)用夯缺,initialize是通過objc_msgSend調(diào)用
調(diào)用時(shí)刻:load是runtime加載類蚤氏、分類的時(shí)候調(diào)用(只會調(diào)用1次),initialize是類第一次接收到消息的時(shí)候調(diào)用踊兜,每一個(gè)類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)

調(diào)用順序:先調(diào)用類的load方法竿滨,先編譯那個(gè)類,就先調(diào)用load。在調(diào)用load之前會先調(diào)用父類的load方法姐呐。分類中l(wèi)oad方法不會覆蓋本類的load方法殿怜,先編譯的分類優(yōu)先調(diào)用load方法。initialize先初始化父類曙砂,之后再初始化子類头谜。如果子類沒有實(shí)現(xiàn)+initialize,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次)鸠澈,如果分類實(shí)現(xiàn)了+initialize柱告,就覆蓋類本身的+initialize調(diào)用。

反觀擴(kuò)展(extension)笑陈,作用是為一個(gè)已知的類添加一些私有的信息际度,必須有這個(gè)類的源碼,才能擴(kuò)展涵妥,它是在編譯器生效的乖菱,所以能直接為類添加屬性或者實(shí)例變量。

由此推想protocol添加屬性會怎么蓬网?

由上面的分析是不是想到了protocol中添加屬性窒所?其實(shí)由protocol的用途也能猜測出:protocol是一系列的協(xié)議,要求代理去實(shí)現(xiàn)帆锋,自己并沒有實(shí)現(xiàn)吵取,方法或?qū)傩远际沁@樣,只是做了聲明要求代理去實(shí)現(xiàn)锯厢。所以添加的屬性也只是聲明其代理有實(shí)現(xiàn)這個(gè)屬性皮官,自身并沒有實(shí)現(xiàn)其getter、setter以及ivar实辑。有興趣的朋友可以實(shí)踐看看捺氢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剪撬,隨后出現(xiàn)的幾起案子讯沈,更是在濱河造成了極大的恐慌,老刑警劉巖婿奔,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缺狠,死亡現(xiàn)場離奇詭異,居然都是意外死亡萍摊,警方通過查閱死者的電腦和手機(jī)挤茄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冰木,“玉大人穷劈,你說我怎么就攤上這事笼恰。” “怎么了歇终?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵社证,是天一觀的道長。 經(jīng)常有香客問我评凝,道長追葡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任奕短,我火速辦了婚禮宜肉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翎碑。我一直安慰自己谬返,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布日杈。 她就那樣靜靜地躺著遣铝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莉擒。 梳的紋絲不亂的頭發(fā)上酿炸,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音啰劲,去河邊找鬼。 笑死檀何,一個(gè)胖子當(dāng)著我的面吹牛蝇裤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播频鉴,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼栓辜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垛孔?” 一聲冷哼從身側(cè)響起藕甩,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎周荐,沒想到半個(gè)月后狭莱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡概作,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年腋妙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讯榕。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骤素,死狀恐怖匙睹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情济竹,我是刑警寧澤痕檬,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站送浊,受9級特大地震影響梦谜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罕袋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一改淑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浴讯,春花似錦朵夏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奈籽,卻和暖如春饥侵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背衣屏。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工躏升, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狼忱。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓膨疏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钻弄。 傳聞我的和親對象是個(gè)殘疾皇子佃却,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

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