找到objc_msgSend
調用方法多艇,打斷點
通過匯編發(fā)現(xiàn)調用
objc_msgSend
,step into
攒至,發(fā)現(xiàn)objc
源碼里面實現(xiàn)通過
objc
源碼找到objc_msgSend
的實現(xiàn)入口,其中不同的架構有不同的實現(xiàn)躁劣,這里我們主要看arm64
匯編分析
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
- 先進入
ENTRY _objc_msgSend
方法 -
cmp p0, #0
檢查對象是否為空p0
上是對象本身迫吐,cmp
比較 - 如果為空則跳轉到
LReturnZero
,b.le
小于等于并跳轉账忘,b.eq
等于并跳轉
4.ldr p13, [x0]
志膀,讀取x0
寄存器的值,賦值給p13
鳖擒,這里的p13實際上是lisa
5.GetClassFromIsa_p16 p13, 1, x0
溉浙,通過isa 獲取到對象的class
6.CacheLookup
通過緩存查找
GetClassFromIsa_p16分析
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
...省略
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
INDEXED_ISA
表示直接是一個raw isa,PACKED_ISA
,需要通過一個掩碼取獲取蒋荚,那么這段代碼實際上只是調用了 ExtractISA
方法戳稽,
進入ExtractISA
#if __has_feature(ptrauth_calls)
.macro ExtractISA
and $0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
xpacd $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
mov x10, $2
movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
autda $0, x10
#endif
.endmacro
#else
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
- __has_feature(ptrauth_calls): 是判斷編譯器是否支持指針身份驗證功能
- ptrauth_calls 指針身份驗證,針對arm64e架構期升;使用Apple A12或更高版本A系列處理器的設備(如iPhone XS惊奇、iPhone XS Max和iPhone XR或更新的設備)支持-arm64e架構
上面的機型的判斷并不影響閱讀源碼,最主要還是and $0, $1, #ISA_MASK
吓妆,
1表示src,也就是p13行拢,
and
是與運算祖秒,p16 = p13 & ISA_MASK
ISA_MASK
是isa里面宏定義的掩碼,所以p16
就是對象的class
指針
CacheLookup分析
CacheLookup,這個方法是快速查找的核心
首先一進來會有架構的判斷竭缝,我們找到真機的方法
CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
ldr p11, [x16, #CACHE]
-
x16
位class
的指針房维,CACHE
的大小為16
個字節(jié),也就是p11位cache位置的指針
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48
- 我們以 A12為準抬纸,
tbnz p11, #0, LLookupPreopt\Function
咙俩,tbnz
:表示如果測試位不為0則跳轉到LLookupPreopt
方法,LLookupPreopt
這個表示從共享緩存去加載(UIKit湿故,F(xiàn)oundation)阿趁,在這里不做過多的分析, -
and p10, p11, #0x0000ffffffffffff
表示p10 = p11 & 0x0000ffffffffffff
, 那么p10
表示bucket
首地址 -
eor p12, p1, p1, LSR #7
坛猪,and p12, p12, p11, LSR #48
這兩句實際上通過sel
拿到bucket index
,p1
表示sel脖阵,LSR
右移,eor
異或操作墅茉,p12 ,其實是bucket的索引
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
-
add p13, p10, p12, LSL #(1+PTRSHIFT)
,p13 = p10 + p12 << 4
,
那么p13 實際上就是當前發(fā)送方法的bucket
命黔,bucket
存儲的是{imp,sel} - 將imp,sel分別保存到p17 和p9就斤,然后遍歷
buckets
去對比悍募,如果發(fā)現(xiàn)有和調用的sel和buckets有相同的,就直接返回imp洋机,也就是CacheHit
緩存命中坠宴, - 如果遍歷都沒有找到,即
cbz p9
位0,即sel為空槐秧,那么跳轉到MissLabelDynamic啄踊,即沒有找到。
用一張圖總結bucket查找過程:
bucket查找.png
緩存沒有找到
緩存沒有找到則會進入__objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend,__objc_msgSend_uncached
繼續(xù)往下走:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
發(fā)現(xiàn)會調用MethodTableLookup
刁标,最后會調用lookUpImpOrForward
方法颠通,發(fā)現(xiàn)
lookUpImpOrForward
在匯編里面沒有找到實現(xiàn),發(fā)現(xiàn)實現(xiàn)在objc-runtime-new.mm里面膀懈,在此就近入方法的慢速查找流程顿锰。
lookUpImpOrForward分析
準備工作
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//準備工作
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
//循環(huán)查找
for (unsigned attempts = unreasonableClassCount();;){
}
return imp;
}
- cls->isInitialized() 檢測類是否初始化
- checkIsKnownClass 檢測類是否已經注冊
- realizeAndInitializeIfNeeded_locked檢測類是否已經實現(xiàn),沒有實現(xiàn)會遞歸去實現(xiàn)父類和元類
二分查找流程(重點)
循環(huán)查找
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
-
cache.isConstantOptimizedCache
先從緩存中判斷是否存在启搂,如果存在直接從緩存中取 -
getMethodNoSuper_nolock
硼控,如果沒有找到從類的methods里面取找
getMethodNoSuper_nolock分析
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
- 可以看出
methods
是一個二維數(shù)字,methods-> mlists
- 真正的查找是在
mlists
里面
繼續(xù)進入search_method_list_inline
在這個里面有兩個方法胳赌,
-
findMethodInSortedMethodList
已經拍好序的 -
findMethodInUnsortedMethodList
沒有排好序的
我們知道如果要進行二分查找牢撼,數(shù)組必須有序绰上,如果是沒有排序的直接遍歷數(shù)組去找桶错。
沒有排序,循環(huán)遍歷
findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName)
{
for (auto& meth : *list) {
if (getName(meth) == sel) return &meth;
}
return nil;
}
排序的,二分查找:
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
- 蘋果的工程是用的很巧妙课锌,通過右移來實現(xiàn)折半查找
- 這里有一個主意的點赏淌,就是我們發(fā)現(xiàn)即使找到了還有一個循環(huán)筛严,
probe--
腮考,這里實際上是因為如果有分類的方法和類的方法一樣的話,會優(yōu)先使用分類的方法曲横,This is required for correct category overrides.
注釋里面也給出了解釋
如果自身的類沒有找到喂柒,就會查找父類,一值到跟類禾嫉。
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
如果都沒有找到就會走消息轉發(fā)forward_imp
總結
上面就是方法的二分查找灾杰,其實消息的發(fā)送還沒有結束,如果自身類熙参,父類吭露,根類(NSObject
)都沒有找到就會走轉發(fā)流程也就是forward_imp
補充(initialize)
還有一個關于initialize方法的調用,我們再調用其他方法是也可能會走initialize
方法,這個方法在我們平時的開發(fā)中也是比較熟悉的
在objc
源碼中可以看到如下的調用順序
lookupimporforward
-> realizeAndInitializeIfNeeded_locked
-> initializeAndLeaveLocked
-> initializeAndMaybeRelock
-> initializeNonMetaClass
-> callInitialize
-> initialize