Category原理解析(1)-load方法和initialize方法詳解

1. Category的底層結(jié)構(gòu)

通過(guò)runtime動(dòng)態(tài)的將分類的方法合并到類對(duì)象或元類對(duì)象中,程序編譯的時(shí):category會(huì)生成,category中的信息會(huì)存儲(chǔ)在struct _category_t中.

struct _category_t {
    const char *name;//類名
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;//實(shí)例方法
    const struct _method_list_t *class_methods;//類方法
    const struct _protocol_list_t *protocols;//協(xié)議
    const struct _prop_list_t *properties;//屬性
};
category的結(jié)構(gòu).png
  1. 從結(jié)構(gòu)體可以知道,有屬性列表,所以分類可以聲明屬性,但是分類只會(huì)生成該屬性對(duì)應(yīng)的get和set的聲明,沒有去實(shí)現(xiàn)該方法。
  2. 結(jié)構(gòu)體沒有成員變量列表,所以不能聲明成員變量。
  3. 分類并不會(huì)改變?cè)蓄惖膬?nèi)存分布的情況,它是在運(yùn)行期間決定的,此時(shí)內(nèi)存的分布已經(jīng)確定,若此時(shí)再添加實(shí)例會(huì)改變內(nèi)存的分布情況,這對(duì)編譯性語(yǔ)言是災(zāi)難,是不允許的。

category的源碼閱讀軌跡:

  • objc-os.mm
  1. _objc_init
  2. map_images
  3. map_images_nolock
  • objc-runtime-new.mm
  1. _read_images
  2. remethodizeClass
  3. attachCategories
  4. attachLists
  5. realloc、memmove、 memcpy

category的源碼分析
① readimges 是讀取模塊的意思,參數(shù)有totalClass是所有的類的意思.
②.remethodizeClass是重新組織類的方法的意思
③.attachCategories是將分類重新規(guī)整,參數(shù)有兩個(gè):一個(gè)類名,一個(gè)是分類的數(shù)組,在內(nèi)部有一個(gè)方法數(shù)組的二維數(shù)組,一個(gè)屬性數(shù)組的二維數(shù)組,一個(gè)協(xié)議的二維數(shù)組,把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中.(attachCategories)
④.attachLists有兩個(gè)參數(shù)一個(gè)所有category的方法列表或?qū)傩粤斜砘騾f(xié)議列表的數(shù)組和第二個(gè)參數(shù)傳入的數(shù)組的count,realloc重新計(jì)算方法列表分配的內(nèi)存大小,memmove移動(dòng)原來(lái)的方法數(shù)據(jù)到末尾,memcpy分類的數(shù)據(jù)插入到原來(lái)數(shù)據(jù)的前面.

順序的總體的概括:

  1. 通過(guò)Runtime加載某個(gè)類的所有Category數(shù)據(jù).
  2. 把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中
    后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面.
  3. 將合并后的分類數(shù)據(jù)(方法、屬性、協(xié)議),插入到類原來(lái)數(shù)據(jù)的前面.

category memmove到memcpy的過(guò)程流程圖:

category-memmove-memcpy(1).png
category-memmove-memcpy(2).png

代碼例子:

// 原來(lái)的類和分類看Demo,這里就不列舉出來(lái)了
// 開始調(diào)用
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *person = [[Person alloc] init];
        [person run];
        [person test];
        [person eat];
        // 通過(guò)runtime動(dòng)態(tài)將分類的方法合并到類對(duì)象、元類對(duì)象zhong
    }
    return 0;
}
log日志.png
Build Phases.png

通過(guò)運(yùn)行結(jié)果可知,分類方法會(huì)覆蓋原來(lái)類對(duì)象方法,并且最后參與編譯的會(huì)在調(diào)用順序最前面。(其實(shí)不是覆蓋,只是尋找方法時(shí)的順序.)

這里有2個(gè)關(guān)于category的問(wèn)題
1.分類中能不能添加屬性,為什么?
不能。類的內(nèi)存布局在編譯時(shí)期就已經(jīng)確定了,category是運(yùn)行時(shí)才加載的早已經(jīng)確定了內(nèi)存布局所以無(wú)法添加實(shí)例變量,如果添加實(shí)例變量就會(huì)破壞category的內(nèi)部布局。
2.為什么說(shuō)category是在運(yùn)行時(shí)加載的?不能添加實(shí)例變量,那為什么能添加屬性?
根據(jù)category_t的結(jié)構(gòu)
①.category小括號(hào)里寫的名字
②.要擴(kuò)展的類對(duì)象,編譯期間這個(gè)值是不會(huì)有的,在app被runtime加載時(shí)才會(huì)根據(jù)name對(duì)應(yīng)到類對(duì)象
③.這個(gè)category所有的-方法
④.這個(gè)category所有的+方法
⑤.這個(gè)category實(shí)現(xiàn)的protocol,比較不常用在category里面實(shí)現(xiàn)協(xié)議,但是確實(shí)支持的
⑥.這個(gè)category所有的property,這也是category里面可以定義屬性的原因,不過(guò)這個(gè)property不會(huì)@synthesize實(shí)例變量,一般有需求添加實(shí)例變量屬性時(shí)會(huì)采用objc_setAssociatedObject和objc_getAssociatedObject方法綁定方法綁定,不過(guò)這種方法生成的與一個(gè)普通的實(shí)例變量完全是兩碼事。

2. +load方法和initialize方法

一. +load

+load方法會(huì)在runtime加載類、分類時(shí)調(diào)用,并且只調(diào)用一次.

  • load方法的調(diào)用順序
  1. 先調(diào)用類的+load.
  2. 按照編譯先后順序調(diào)用(先編譯,先調(diào)用).
  3. 調(diào)用子類的+load之前會(huì)先調(diào)用父類的+load.
  4. 再調(diào)用分類的+load.
  • 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
  1. load方法為什么類和分類都調(diào)用,原因是load方法是根據(jù)方法地址直接調(diào)用.

代碼佐證:


category的調(diào)用順序.png
  • objc4源碼解讀過(guò)程:objc-os.mm
  1. _objc_init
  2. load_images
  3. prepare_load_methods
    schedule_class_load
    add_class_to_loadable_list
    add_category_to_loadable_list
  4. call_load_methods
    call_class_loads
    call_category_loads
    (*load_method)(cls, SEL_load)
  • +load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過(guò)objc_msgSend函數(shù)調(diào)用。

二. +initialize方法

+initialize方法會(huì)在類第一次接收到消息時(shí)調(diào)用.(initialize應(yīng)該是objc_msgSend實(shí)現(xiàn)的)

  • initialize調(diào)用順序:
  1. 先調(diào)用父類的+initialize,再調(diào)用子類的+initialize.
  2. (先初始化父類,再初始化子類,每個(gè)類只會(huì)初始化1次).
  3. 如果分類實(shí)現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用.
  • objc4源碼解讀過(guò)程
  1. objc-msg-arm64.s
    objc_msgSend
  2. objc-runtime-new.mm
    class_getInstanceMethod
    lookUpImpOrNil
    lookUpImpOrForward
    _class_initialize
    callInitialize
    objc_msgSend(cls, SEL_initialize)

調(diào)用的詳情解析:
①.class_getInstanceMethod這個(gè)是找到對(duì)象方法的方法.
②.lookUpImporNil.
③.lookUpImporForward在查找方法的時(shí)候調(diào)用_class_initialize看類是否初始化,如果沒有初始化就調(diào)用callInitialize初始化.
④._class_initialize 是一個(gè)遞歸來(lái)判斷父類是否初始化.

調(diào)用順序證明:

每個(gè)類都實(shí)現(xiàn)了+ initialize方法.png
并不是每一個(gè)類都實(shí)現(xiàn)了+ initialize方法.png

解析:
1.[Student alloc]會(huì)調(diào)用+initialize方法,因?yàn)樗懈割怭erson,所以先調(diào)用Person的+initialize方法,又因?yàn)榉诸愒谇懊?,所以調(diào)用了Person(Test2)的+initialize方法。但是他自己本身沒有實(shí)現(xiàn)+initialize方法,所以會(huì)去父類查找,然后分類方法在前面,所以調(diào)用了Person(Test2)的+initialize方法。
2.[Teacher alloc]會(huì)調(diào)用+initialize方法,因?yàn)樗懈割怭erson,所以先調(diào)用Person的+initialize方法,但是前面已經(jīng)初始化過(guò)了,所以跳過(guò),調(diào)用自己的+initialize方法,但是因?yàn)樗约簺]有實(shí)現(xiàn)+initialize方法,所以調(diào)用父類的+initialize方法,又因?yàn)榉诸惙椒ㄔ谇懊?,所以調(diào)用Person(Test) +initialize方法。
3.[Person alloc],因?yàn)榍懊嬉呀?jīng)初始化過(guò)了,所以不會(huì)再調(diào)+initialize方法,所以這里不打印。

+initialize和+load的很大區(qū)別是,+initialize是通過(guò)objc_msgSend進(jìn)行調(diào)用的,所以有以下特點(diǎn):

  1. 如果子類沒有實(shí)現(xiàn)+initialize,會(huì)調(diào)用父類的+initialize(所以父類的+initialize可能會(huì)被調(diào)用多次).
  2. 如果分類實(shí)現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用.
                            想了解更多iOS學(xué)習(xí)知識(shí)請(qǐng)聯(lián)系:QQ(814299221)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370