objc_msgSend 慢速查找流程分析
前一篇我們分析了匯編快速查找,如果沒(méi)有找到,就會(huì)進(jìn)入CheckMiss
或者JumpMiss
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
然后進(jìn)入到__objc_msgSend_uncached
,
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup //方法表中查找
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
MethodTableLookup
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward//跳轉(zhuǎn)到_lookUpImpOrForward
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
imp找不到會(huì)跳轉(zhuǎn)到_lookUpImpOrForward
, _lookUpImpOrForward
沒(méi)有.macro
宏爸业,說(shuō)明跳轉(zhuǎn)到c
或者c++
的代碼中果覆。我們可以通過(guò)匯編調(diào)試
驗(yàn)證一下宰闰,添加斷點(diǎn)存捺,點(diǎn)擊control + stepinto
打開(kāi)debug
--> Debug WorkFlow
--> always show disassembly
斷住objc_msgSend贼邓,繼續(xù)control + stepInto, 斷住_objc_msgSend_uncached,繼續(xù)control + stepInto
最后走到的是lookUpImpOrForward
热鞍,這里并不是匯編實(shí)現(xiàn)葫慎,而是C/C++實(shí)現(xiàn)
- 在
C/C++
中調(diào)用匯編
,在匯編
的定義中要加一個(gè) _ 下劃線 - 在
匯編
中調(diào)用C/C++
薇宠,C/C++
的函數(shù)定義中要減一個(gè) _ 下劃線
慢速查找的C/C++部分
全局搜索lookUpImpOrForward
,在最新的objc-runtime-new.mm
找到偷办,是一個(gè)C
函數(shù),我們來(lái)看代碼昼接,我加了相應(yīng)的注釋
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
//在多線程情況下方法可能會(huì)緩存
if (fastpath(behavior & LOOKUP_CACHE)) {
//通過(guò)匯編獲取imp
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//線程加鎖
runtimeLock.lock();
//檢查是否是被認(rèn)可的對(duì)象爽篷,已知類(或者是內(nèi)置到二進(jìn)制文件中,或者合法注冊(cè)通過(guò))
checkIsKnownClass(cls);
//如果類沒(méi)有實(shí)現(xiàn)
if (slowpath(!cls->isRealized())) {
//實(shí)現(xiàn)類,內(nèi)部父類繼承鏈向上依次實(shí)現(xiàn)慢睡。確保后面的方法查找可以進(jìn)行逐工。
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
//判斷類是否初始化,如果沒(méi)有漂辐,需要先初始化
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
//*unreasonableClassCount為類的任何迭代提供一個(gè)上限泪喊,在運(yùn)行時(shí)元數(shù)據(jù)被破壞時(shí)防止自旋。
//這是個(gè)無(wú)限循環(huán)髓涯,要使用break或goto來(lái)跳出循環(huán)
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
// 本類進(jìn)行一次imp查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
// curClass向繼承鏈傳遞賦值袒啼,當(dāng)找到nil的時(shí)候讓 imp = forward_imp, break,終止循環(huán)
if (slowpath((curClass = curClass->superclass) == 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.
// 如果父類鏈存在循環(huán),則報(bào)錯(cuò)蚓再。
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
//匯編查找父類緩存
imp = cache_getImp(curClass, sel);
// 當(dāng)imp == forward_imp
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;
}
// 如果在父類中找到了滑肉,直接 goto done
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
//這個(gè)& ^= 這個(gè)算法是為了讓這段代碼只執(zhí)行一次
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
// 進(jìn)入動(dòng)態(tài)方法決議
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
總結(jié)一下上述過(guò)程:
- 找一下緩存,排除多線程影響
- 判斷類是否被認(rèn)可的類摘仅,已知類
- 判斷類是否已實(shí)現(xiàn)
- 判斷類是否已初始化
- 進(jìn)入for死循環(huán)靶庙,先進(jìn)行一次本類的方法查找(二分查找),讓臨時(shí)類curClass指向父類娃属,通過(guò)匯編依次向上查找(cache_getImp)六荒,直到curClass找到nil ,將imp賦值為_(kāi)objc_msgForward_impcache矾端,然后break跳出循環(huán)掏击。
- 進(jìn)行一次動(dòng)態(tài)方法決議,resolveMethod_locked 判斷秩铆,resolveInstanceMethod和resolveInstanceMethod砚亭,是否有做處理,如果處理了豺旬,重新找一遍imp,若果沒(méi)有處理钠惩,繼續(xù)lookUpImpOrForward柒凉。
- 消息快速轉(zhuǎn)發(fā),
- 消息的慢速轉(zhuǎn)發(fā)
上面是結(jié)合代碼的描述過(guò)程族阅,我只描述了關(guān)鍵部分,接下來(lái)我們來(lái)看一下流程圖
我們來(lái)看看二分查找算法的實(shí)現(xiàn):
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *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)probe->name;
if (keyValue == probeValue) {
//如果分類有相同方法膝捞,取分類的方法
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
二分查找是一種非常高效的查找算法坦刀,它的時(shí)間復(fù)雜度是O(logn),O(logn) 這種對(duì)數(shù)時(shí)間復(fù)雜度蔬咬。這是一種極其高效的時(shí)間復(fù)雜度鲤遥,因?yàn)?logn 是一個(gè)非常“恐怖”的數(shù)量級(jí)林艘,即便 n 非常非常大盖奈,對(duì)應(yīng)的 logn 也很小。比如 n 等于 2 的 32 次方狐援,這個(gè)數(shù)很大了吧钢坦?大約是 42 億。也就是說(shuō)啥酱,如果我們?cè)?42 億個(gè)數(shù)據(jù)中用二分查找一個(gè)數(shù)據(jù)爹凹,最多需要比較 32 次。
我們?cè)賮?lái)看看動(dòng)態(tài)方法決議
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// 給你一次機(jī)會(huì),添加imp
runtimeLock.unlock();
//當(dāng)前類不是元類
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
//當(dāng)前類是元類
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
// 類方法在元類里就是實(shí)例方法镶殷,我們無(wú)法在元類里面寫(xiě)方法來(lái)處理禾酱,但是在NSObject里面是可以處理的,也就是實(shí)例方法
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
// resolve_sel 沒(méi)有實(shí)現(xiàn)返回
return;
}
//發(fā)送一次resolve_sel消息,也就是調(diào)用一次resolve_sel方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
// 再找一次sel的imp;
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
首先判斷resolveInstanceMethod是否實(shí)現(xiàn)颤陶,沒(méi)有時(shí)間直接返回颗管,如果有實(shí)現(xiàn),發(fā)送消息resolveInstanceMethod滓走,即調(diào)用我們自己處理的resolveInstanceMethod方法忙上,然后進(jìn)入慢速查找lookUpImpOrNil查找IMP,這里找到IMP只是為了打印消息闲坎,然后再進(jìn)入lookUpImpOrNil查找IMP 返回IMP
動(dòng)態(tài)方法決議的處理
#import "LGPerson.h"
#import <objc/message.h>
@implementation LGPerson
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayNB{
NSLog(@"%s",__func__);
}
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
if (sel == @selector(sayNB)) {
//注意這里類方法要添加到元類里面
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
在類方法查找imp的過(guò)程中疫粥,最終找到NSObject,那么我們想可以統(tǒng)一將動(dòng)態(tài)方法決議寫(xiě)到NSObject的分類中
implementation NSObject (LG)
// 調(diào)用方法的時(shí)候 - 分類
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
else if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method sayMMethod = c lass_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
/**
1: 分類 - 便利
2: 方法 - lg_model_tracffic
- lg - model home - 奔潰 - pop Home
- lg - mine - mine
切面 - SDK - 上傳
3: AOP - 封裝SDK - 不處理
4: 消息轉(zhuǎn)發(fā) -
*/
@end
一般我們不在這一步做處理腰懂,因?yàn)橛锌赡鼙蛔宇悢r截梗逮。我們繼續(xù)探索動(dòng)態(tài)方法決議之后還走了哪些
在源碼中有打印消息發(fā)送的開(kāi)關(guān),我們?cè)贠C中使用要用extern 開(kāi)放出來(lái)
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
LGPerson *person = [LGPerson alloc];
[person sayHello];
instrumentObjcMessageSends(YES);
NSLog(@"Hello, World!");
}
return 0;
}
通過(guò)logMessageSend
源碼绣溜,消息發(fā)送打印信息存儲(chǔ)在/tmp/msgSends/ 目錄慷彤,如下所示我們找到打印文件:
- 發(fā)送了兩次動(dòng)態(tài)方法決議:resolveInstanceMethod方法
- 發(fā)送了兩次消息快速轉(zhuǎn)發(fā):forwardingTargetForSelector方法
- 發(fā)送了兩次消息慢速轉(zhuǎn)發(fā):methodSignatureForSelector + resolveInstanceMethod
我們?cè)谕ㄟ^(guò)bt命令看一下調(diào)用堆棧
我們發(fā)現(xiàn)___forwardingTarget___
在CoreFoundation
框架中,但是這個(gè)框架蘋(píng)果并沒(méi)有開(kāi)源怖喻,我們可以通過(guò)image list底哗,讀取整個(gè)鏡像文件,然后搜索CoreFoundation,查看其可執(zhí)行文件的路徑锚沸,找到文件跋选,通過(guò)hopper進(jìn)行反匯編
判斷是否可以響應(yīng),發(fā)送消息看是否有接收者哗蜈。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
// GM sayHello - anInvocation - 漂流瓶 - anInvocation
anInvocation.target = [LGStudent alloc];
// anInvocation 保存 - 方法
[anInvocation invoke];
}
我們可以看到 匯編又調(diào)用了一次動(dòng)態(tài)方法決議前标,這也是為什么resolveInstanceMethod走兩遍的原因。