調(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)用棧:
- 圖注 : 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
方法.