+load?VS?+initialize

調(diào)用時(shí)機(jī)


+load

通過上一篇 從 MachO 加載到對象創(chuàng)建! 可以了解到:

  • DYLD 在初始化主程序時(shí)發(fā)起 load_images 回調(diào), 從而調(diào)用所有類以及分類的 +load 方法 (當(dāng)然, 還有屬性 / 協(xié)議, 依次是 方法-屬性-協(xié)議-分類).

+initialize

  • 新建類 WXHuman 并重寫 +initialize 方法;
  • 因?yàn)?+initialize 是類方法, 調(diào)用的時(shí)候是通過 objc_msgSend 調(diào)用, 所以在新建類且沒有對該類作任何方法調(diào)用的時(shí)候, 此時(shí) +initialize 不會(huì)調(diào)用 !
  • 在調(diào)用 [WXHuman alloc] 時(shí), runtime 通過 objc_msgSend 給 Person 類對象發(fā)送 alloc 消息, 當(dāng)斷點(diǎn)調(diào)試 +initialize 方法時(shí),可以看到調(diào)用棧:
initialize 調(diào)用棧
  • 圖注 : 1._class_initialize(Class) 調(diào)用了兩次是因?yàn)樵?main() 函數(shù)中創(chuàng)建的對象是子類對象 WXPerson 而不是 WXHuman.
    2._CALLING_SOME_+initialize_METHOD 方法僅僅是為了在調(diào)用堆棧中方便跟蹤提供的消息.

因?yàn)榉椒ㄕ{(diào)用會(huì)轉(zhuǎn)換為消息發(fā)送, 所以會(huì)看到在發(fā)送消息的時(shí)候調(diào)用了 _objc_msgSend_uncached函數(shù).
那么這個(gè)方法又是怎么被調(diào)起的?
注: 因?yàn)橄l(fā)送會(huì)反復(fù)被調(diào)用, 考慮性能所以蘋果用匯編實(shí)現(xiàn)了 objc_msg 機(jī)制.
所以 :

_objc_msgSend 匯編片段 (取自 objc-msg-arm.s, 感覺好理解些, 大同小異) :

    ENTRY _objc_msgSend     // 入口

    cbz r0, LNilReceiver_f  // **獲取方法調(diào)用者

    ldr r9, [r0]            // r9 = self->isa
    GetClassFromIsa         // r9 = class -- **line 307 - .macro GetClassFromIsa
    CacheLookup NORMAL      // line 251 - .macro CacheLookup **緩存查找
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    bx  r12                 // call imp -- **緩存找到并執(zhí)行

    // 沒有找到緩存, 則會(huì)跳轉(zhuǎn)到 __objc_msgSend_uncached
    CacheLookup2 NORMAL

    // cache miss
    ldr r9, [r0]             // r9 = self->isa
    GetClassFromIsa          // r9 = class
    b   __objc_msgSend_uncached // line 691 -- STATIC_ENTRY __objc_msgSend_uncached

// Receiver 為 nil
LNilReceiver:
    // r0 is already zero
    mov r1, #0
    mov r2, #0
    mov r3, #0
    FP_RETURN_ZERO  // 返回 nil
    bx  lr          // 跳轉(zhuǎn)原執(zhí)行入口

    END_ENTRY _objc_msgSend  // 結(jié)束
  • 大概意思:
    .獲取方法調(diào)用者
    .獲取方法調(diào)用者 isa
    .從緩存查找方法
    .緩存找到就執(zhí)行 imp
    .沒有找到就會(huì)調(diào)用 __objc_msgSend_uncached
    .返回 nil
  • 在執(zhí)行 __objc_msgSend_uncached 時(shí), 又會(huì)調(diào)用 MethodTableLookup 函數(shù)
    STATIC_ENTRY __objc_msgSend_uncached

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r9 is the class to search

    // line 649 .macro MethodTableLookup 查找方法列表
    // returns IMP in r12 
    MethodTableLookup NORMAL    
    bx  r12

    END_ENTRY __objc_msgSend_uncached
  • 再看 MethodTableLookup :
.macro MethodTableLookup

    stmfd   sp!, {r0-r3,r7,lr}
    add r7, sp, #16
    sub sp, #8          // align stack
    FP_SAVE

.if $0 == NORMAL
    // receiver already in r0
    // selector already in r1
.else
    mov     r0, r1          // receiver
    mov     r1, r2          // selector
.endif
    mov r2, r9          // class to search

    blx __class_lookupMethodAndLoadCache3
    mov r12, r0         // r12 = IMP

.if $0 == NORMAL
    cmp r12, r12        // set eq for nonstret forwarding
.else
    tst r12, r12        // set ne for stret forwarding
.endif

    FP_RESTORE
    add sp, #8          // align stack
    ldmfd   sp!, {r0-r3,r7,lr}

.endmacro

通過函數(shù)調(diào)用棧可以看到匯編最終調(diào)用到 _class_lookupMethodAndLoadCache3 函數(shù) (objc-msg-x86_64.s 有提示), 定義為 message dispatcher(消息調(diào)度者), 由于類也可以當(dāng)做對象, 所以在這個(gè)方法中需要通過條件斷點(diǎn)進(jìn)行調(diào)試.
最終該方法中又會(huì)調(diào)用:

lookUpImpOrForward(cls, 
                   sel, 
                   obj,
                   YES/*initialize*/, 
                   NO/*cache*/, 
                   YES/*resolver*/);

該函數(shù)大致實(shí)現(xiàn):

  • cache標(biāo)記緩存查找(當(dāng)前為 NO)

  • 保證類實(shí)現(xiàn):

    if (!cls->isRealized()) {
        realizeClass(cls);
    }
    
  • 然后經(jīng)過 !cls->isInitialized() 判斷 (initialize 傳入為 YES),
    如果 cls(不是元類) 沒有初始化, 則會(huì)進(jìn)行類初始化 _class_initialize, 該類中首先會(huì)一直查找其父類(只會(huì)實(shí)現(xiàn)父類)

  • 然后經(jīng)過一系列判斷后便會(huì)調(diào)用 callInitialize ==> ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize).
    至此, 通過 objc_msgSend 給 cls 發(fā)送 initialize 消息, 調(diào)用完畢!

小結(jié) :

+load 方法在程序加載時(shí)已經(jīng)執(zhí)行, 比 main() 函數(shù)要早.
+initialize (官方解釋: Initializes the class before it receives its first message.), 顯然決定 +initialize 方法調(diào)用時(shí)機(jī)是在第一次調(diào)用 cls 的方法前;

注意 : 像 NSObject (不能重載 +initialize), NSThread 這樣的類在 main() 函數(shù)前就會(huì)調(diào)用其方法, 所以調(diào)用 +initialize 時(shí)機(jī)也會(huì)在 main() 函數(shù)之前, 甚至在 +load 前調(diào)用!
所以對于自定義類 +load+initialize 要早很多, 但是有些系統(tǒng)類的 +initialize 方法會(huì)在 +load 之前調(diào)用!

: 系統(tǒng)調(diào)用 +load 方法不會(huì)觸發(fā) +initialize方法, 而手動(dòng)調(diào)用 +load 方法時(shí)會(huì)觸發(fā) +initialize 方法.
eg: 在 +load 方法里面調(diào)用 [super load]; 會(huì)發(fā)現(xiàn)在該類 +load 時(shí)會(huì)發(fā)送 [super load] 消息, 所以會(huì)先調(diào)用父類的 +initialize 方法 (手動(dòng)調(diào)用, 優(yōu)先查找分類) , 然后才是父類的 +load, 再然后才是當(dāng)前類的 +load 方法.
如圖 :

2019-03-28   +[NSThread(initialize) initialize] - DYLD 加載時(shí)調(diào)用,分類重載
2019-03-28   +[WXHuman load] - DYLD 加載時(shí)調(diào)用
2019-03-28   +[WXHuman(One) initialize] - 優(yōu)先分類查找
2019-03-28   +[WXHuman(One) load] - 手動(dòng)調(diào)用,優(yōu)先分類查找
2019-03-28   +[WXPerson load] - [super load] 
2019-03-28   +[WXHuman(One) load] - DYLD 加載時(shí)調(diào)用
2019-03-28   +[WXPerson(One) load] - DYLD 加載時(shí)調(diào)用
2019-03-28   +[NSThread(initialize) load] - DYLD 加載時(shí)調(diào)用
2019-03-28   +[WXPerson(Initialize) load] - DYLD 加載時(shí)調(diào)用
2019-03-28   main 方法入口打印
2019-03-28   +[WXPerson(Initialize) initialize] - DYLD 加載時(shí)調(diào)用

調(diào)用


新建 WXPerson : WXHuman / WXPerson(One) / WXPerson(Two) / WXHuman(One) 類, 這些類均重寫了 +load+initialize 方法:

2019-03-27 18:54:40.889322+0800 +[WXHuman load]
2019-03-27 18:54:40.889560+0800 +[WXPerson load]
2019-03-27 18:54:40.889575+0800 +[WXPerson(Initialize) load]
2019-03-27 18:54:40.889586+0800 +[WXPerson(One) load]
2019-03-27 18:54:40.889711+0800 +[WXHuman(One) initialize]
2019-03-27 18:54:40.889742+0800 +[WXPerson(One) initialize]

因?yàn)?+load 加載是遍歷所有的類(只要是類)進(jìn)行查找的, 所以只要實(shí)現(xiàn)就會(huì)調(diào)用;
+initialize 則會(huì)被分類重載方法所覆蓋 (在 methodizeClass(cls) 方法實(shí)現(xiàn)時(shí)調(diào)用 attachList 方法時(shí),分類添加是在類之后, 且添加 list 操作是向前添加, 所以在查找方法的時(shí)候會(huì)優(yōu)先找到分類的方法), 所以只會(huì)調(diào)用分類的初始化方法, 當(dāng)然可以調(diào)用 [super initialize] 調(diào)起父類 +initialize 方法.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馒胆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌仲翎,老刑警劉巖烹植,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叠纷,死亡現(xiàn)場離奇詭異肮蛹,居然都是意外死亡勺择,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門蔗崎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酵幕,“玉大人扰藕,你說我怎么就攤上這事缓苛。” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵未桥,是天一觀的道長笔刹。 經(jīng)常有香客問我,道長冬耿,這世上最難降的妖魔是什么舌菜? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮亦镶,結(jié)果婚禮上日月,老公的妹妹穿的比我還像新娘。我一直安慰自己缤骨,他們只是感情好爱咬,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绊起,像睡著了一般精拟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上虱歪,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天蜂绎,我揣著相機(jī)與錄音,去河邊找鬼笋鄙。 笑死师枣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的局装。 我是一名探鬼主播坛吁,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼铐尚!你這毒婦竟也來了拨脉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宣增,失蹤者是張志新(化名)和其女友劉穎玫膀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爹脾,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帖旨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灵妨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片解阅。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泌霍,靈堂內(nèi)的尸體忽然破棺而出货抄,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布蟹地,位于F島的核電站积暖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怪与。R本人自食惡果不足惜夺刑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望分别。 院中可真熱鬧遍愿,春花似錦、人聲如沸耘斩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煌往。三九已至倾哺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刽脖,已是汗流浹背羞海。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曲管,地道東北人却邓。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像院水,于是被迫代替她去往敵國和親腊徙。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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