背景
如果大家用clang編譯編譯過(guò)oc的代碼,你會(huì)發(fā)現(xiàn)oc的所有方法調(diào)用最終都都轉(zhuǎn)換成了objc_msgSend,而它內(nèi)部是如何實(shí)現(xiàn)的呢,對(duì)于我們開(kāi)發(fā)過(guò)程中經(jīng)常遇到的unrecognized selector sent to instance這個(gè)方法未實(shí)現(xiàn)異常又是如何出現(xiàn)的呢,今天我們就來(lái)剖析一下整個(gè)流程吧~
先總結(jié)一下objc_msgSend整體流程的四個(gè)階段:
快速查找慢速查找動(dòng)態(tài)方法決議消息轉(zhuǎn)發(fā)
下面我們將從這四個(gè)階段還逐步分析其具體流程和實(shí)現(xiàn)。
一、快速查找
首先看objc_msgSend這個(gè)名字大概就能猜到它是屬于runtime中的方法,所以在runtime源碼層面搜索objc_msgSend,發(fā)現(xiàn)其是使用匯編語(yǔ)言實(shí)現(xiàn)的,并且根據(jù)不同的架構(gòu)有不同的實(shí)現(xiàn),這里我們直接看ios的架構(gòu)objc-msg-arm64.s中的實(shí)現(xiàn):
#if SUPPORT_TAGGED_POINTERS
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
#endif
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 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
ENTRY表示入口,cmp、b.le、b.eq這些都是一些判空處理,基本不用關(guān)心。所以關(guān)鍵流程在于,先通過(guò)GetClassFromIsa_p16獲取isa,拿到后進(jìn)入LGetIsaDone流程,取isa的流程如下:
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
比較關(guān)鍵就是這句and p16, $0, #ISA_MASK,其實(shí)在前面的isa結(jié)構(gòu)分析中我們看到過(guò)獲取對(duì)象的isa代碼:
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
可以看到這里只是用匯編語(yǔ)言在做同樣的事情,瞬間感覺(jué)匯編也沒(méi)那么難了,有木有~
接下來(lái),在拿到isa后會(huì)調(diào)用CacheLookup方法并傳入NORMAL參數(shù):
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
這里是在通過(guò)指針平移后遍歷Class中的cache,比較其中的bucket->sel和當(dāng)前傳進(jìn)來(lái)的_cmd。其實(shí)通過(guò)c語(yǔ)法也可以實(shí)現(xiàn),只是這里使用了匯編。后續(xù)流程有三種情況CacheHit、CheckMiss、JumpMiss。
先看CacheHit:
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x12, x1, x16 // authenticate imp and re-sign as IMP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
傳入的是NORMAL,直接調(diào)用了方法。
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
由于傳入?yún)?shù)也是NORMAL所以都會(huì)調(diào)用到__objc_msgSend_uncached。
__objc_msgSend_uncached繼續(xù)跟蹤代碼如下:
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的實(shí)現(xiàn)
.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
// 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
.endmacro
可以看到__objc_msgSend_uncached是調(diào)用了MethodTableLookup方法,而MethodTableLookup經(jīng)過(guò)一系統(tǒng)的指針操作后調(diào)用了_lookUpImpOrForward方法進(jìn)入到c語(yǔ)言層面的方法查找,這也就是后續(xù)的慢速查找流程,至此匯編語(yǔ)言層面的cache中快速查找流程結(jié)束。
二、慢速查找
全局搜索lookUpImpOrForward方法實(shí)現(xiàn):
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();
// 多線程情況下,方法的實(shí)現(xiàn)在另外的線程中被加載過(guò)了,所以先查詢一次,如果查詢到了直接到done_nolock
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
checkIsKnownClass(cls);
//如果類(lèi)還未實(shí)現(xiàn),會(huì)先加載類(lèi)的一些初始化信息,例如繼承鏈等
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
//死循環(huán)遍歷繼承鏈上的所有的類(lèi)對(duì)象,查看有沒(méi)有對(duì)應(yīng)的方法實(shí)現(xiàn)
for (unsigned attempts = unreasonableClassCount();;) {
//1.先查找當(dāng)前類(lèi)的bits信息中methods,找到的話跳轉(zhuǎn)到done
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
//2.如果當(dāng)前類(lèi)中未找到,則把curClass指向其父類(lèi),進(jìn)入下一個(gè)循環(huán),直到找到根NSObject的父類(lèi)nil為止
if (slowpath((curClass = curClass->superclass) == nil)) {
//直到根NSObject還未找到對(duì)應(yīng)方法實(shí)現(xiàn),指向imp為默認(rèn)值
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.");
}
//此處屬于優(yōu)化項(xiàng),通過(guò)匯編在cache中快速查找,如果父類(lèi)中有方法實(shí)現(xiàn),直接跳出循環(huán)
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
goto done;
}
}
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;//確保方法只進(jìn)來(lái)一次
//在上面的循環(huán)結(jié)束后如果還沒(méi)有找到方法實(shí)現(xiàn),將會(huì)調(diào)用resolveMethod_locked進(jìn)入動(dòng)態(tài)方法決議
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//如果找到方法的實(shí)現(xiàn),會(huì)進(jìn)行緩存并記錄日志
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;
}
上面的流程主要是在類(lèi)的繼承鏈上找方法的實(shí)現(xiàn),如果找到則會(huì)調(diào)用log_and_fill_cache進(jìn)行緩存并返回,如果循環(huán)結(jié)束了還是未找到會(huì)把imp指向默認(rèn)值_objc_msgForward_impcache,這將會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程。并且會(huì)調(diào)用resolveMethod_locked方法進(jìn)入動(dòng)態(tài)方法決議流程。
其中有一個(gè)比較關(guān)鍵方法調(diào)用getMethodNoSuper_nolock,從當(dāng)前類(lèi)中查找方法實(shí)現(xiàn),查看其方法實(shí)現(xiàn):
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
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;
}
return nil;
}
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
//二分查找進(jìn)行遍歷方法實(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;
//循環(huán)時(shí)count每次除2
for (count = list->count; count != 0; count >>= 1) {
//把probe指向base偏移總量的一半位置
probe = base + (count >> 1);
//進(jìn)行比較,如果直接相等,還要判斷分類(lèi)的情況
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//此處是因?yàn)榉诸?lèi)的同名方法被加載進(jìn)來(lái)后是排在前面的,為了能
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
//如果要查找的值比probeValue大,則需要把base指針后移,否則count除2即可
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
三、動(dòng)態(tài)方法決議
先看一下動(dòng)態(tài)方法決議resolveMethod_locked的方法的實(shí)現(xiàn):
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//經(jīng)過(guò)上面的resolveInstanceMethod和resolveClassMethod這兩個(gè)方法調(diào)用,給了開(kāi)發(fā)者一次機(jī)會(huì)重新實(shí)現(xiàn)sel的方法的機(jī)會(huì),如果開(kāi)發(fā)者實(shí)現(xiàn)了,則此處再次把imp查找出來(lái)返回,則慢速查找流程中的imp為新的方法實(shí)現(xiàn)了。
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
如果不是元類(lèi)(表示調(diào)用的是對(duì)象方法)則進(jìn)入resolveInstanceMethod,如果是元類(lèi)(表示調(diào)用的是類(lèi)方法)則先調(diào)用resolveClassMethod,如果還是沒(méi)找到則再調(diào)用resolveInstanceMethod。在resolveClassMethod和resolveClassMethod這兩個(gè)方法中給了開(kāi)發(fā)者一次動(dòng)態(tài)實(shí)現(xiàn)imp的機(jī)會(huì),如果在這個(gè)過(guò)程中實(shí)現(xiàn)了方法,則最后返回新的imp給上層調(diào)用方。
resolveInstanceMethod和resolveClassMethod代碼實(shí)現(xiàn):
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//先看一下當(dāng)前類(lèi)有沒(méi)有實(shí)現(xiàn)resolveInstanceMethod方法,lookUpImpOrNil也會(huì)在當(dāng)前類(lèi)的繼承鏈上找,NSObject類(lèi)肯定實(shí)現(xiàn)了,所以不會(huì)return
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
//直接調(diào)用resolveInstanceMethod方法,所以如果你在自定義的類(lèi)中重寫(xiě)resolveInstanceMethod方法,則會(huì)調(diào)用到你的實(shí)現(xiàn)中來(lái)
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
//調(diào)用完resolveInstanceMethod方法后再嘗試查找sel方法的實(shí)現(xiàn),如果找到了
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));
}
}
}
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);
}
}
//注意此時(shí)cls傳進(jìn)來(lái)的是元類(lèi),所以此處要取nonmeta(類(lèi)),因?yàn)閛bjc_msgSend的第一個(gè)參數(shù)是方法接收者,應(yīng)該是我們的自定義類(lèi)
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));
}
}
}
至此,動(dòng)態(tài)方法決議流程結(jié)束,在這個(gè)過(guò)程中如果用戶把sel對(duì)應(yīng)的imp實(shí)現(xiàn)了,那整個(gè)方法查找流程結(jié)束,否則imp將返回默認(rèn)的_objc_msgForward_impcache,則會(huì)進(jìn)入到消息轉(zhuǎn)發(fā)流程,再給開(kāi)發(fā)者兩次處理消息的機(jī)會(huì)。
動(dòng)態(tài)方法決議的思考
從上面的動(dòng)態(tài)方法決議流程來(lái)看,不管是調(diào)用對(duì)象方法還是類(lèi)方法,最后都會(huì)調(diào)用resolveInstanceMethod方法,為什么我調(diào)用一個(gè)類(lèi)方法也需要走到resolveInstanceMethod中來(lái)呢?我是這么理解的:類(lèi)的繼承鏈往上會(huì)找到根NSObject,元類(lèi)的繼承鏈往上也會(huì)找到根NSObject,而開(kāi)發(fā)者可以選擇在當(dāng)前類(lèi)中實(shí)現(xiàn)resolveClassMethod方法,或者重寫(xiě)NSObject的resolveInstanceMethod(用category),所以這里相當(dāng)于蘋(píng)果給了開(kāi)發(fā)者額外的一次機(jī)會(huì)來(lái)處理類(lèi)方法的調(diào)用。
基于上面的分析,我們可以有這樣一種猜測(cè),是否能增加一個(gè)NSObject的category,重寫(xiě)其resolveInstanceMethod,這樣所有的未實(shí)現(xiàn)方法不都可以處理了嗎?
//定義一個(gè)LGPerson類(lèi),聲明一個(gè)對(duì)象方法和類(lèi)方法,但都不實(shí)現(xiàn),實(shí)現(xiàn)另外兩個(gè)方法
@interface LGPerson : NSObject
- (void)say666;
+ (void)sayNB;
@end
@implementation LGPerson
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
@end
//NSObject+Fix中
+ (BOOL)resolveInstanceMethod:(SEL)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 = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
在main函數(shù)中測(cè)試代碼如下:
LGPerson *person = [LGPerson alloc];
[person say666];
[LGPerson sayNB];
//程序打印結(jié)果
2020-09-28 09:53:50.739887+0800 002-testObjc[5875:2952619] say666 來(lái)了
2020-09-28 09:53:50.740931+0800 002-testObjc[5875:2952619] -[LGPerson sayMaster]
2020-09-28 09:54:04.081564+0800 002-testObjc[5875:2952619] +[LGPerson lgClassMethod]
Program ended with exit code: 0
但是調(diào)過(guò)過(guò)程發(fā)現(xiàn),系統(tǒng)底層的方法調(diào)用也會(huì)進(jìn)來(lái),所以此方法侵入性較強(qiáng),可能會(huì)影響系統(tǒng)層面的有影響,慎用!
四、消息轉(zhuǎn)發(fā)
在進(jìn)入消息轉(zhuǎn)發(fā)流程之前我們先看一下這種情況,如果在上面的動(dòng)態(tài)方法決議中還是沒(méi)有對(duì)應(yīng)的imp實(shí)現(xiàn),則整個(gè)lookUpImpOrForward方法將返回默認(rèn)的,_objc_msgForward_impcache,該方法是實(shí)現(xiàn)在匯編層面:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
可以看到最終調(diào)用的方法是__objc_forward_handler,而全局搜索它的定義在runtime源碼中有個(gè)賦值操作objc_defaultForwardHandler,默認(rèn)消息轉(zhuǎn)發(fā)處理也就是我們經(jīng)常遇到的方法未實(shí)現(xiàn)異常:
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
既然有默認(rèn)消息轉(zhuǎn)發(fā),那肯定有非默認(rèn)的情況。這也是系統(tǒng)留給開(kāi)發(fā)者最后的二次處理消息的機(jī)會(huì)。消息轉(zhuǎn)發(fā)分為兩個(gè)步驟:
快速轉(zhuǎn)發(fā)慢速轉(zhuǎn)發(fā)
快速轉(zhuǎn)發(fā)
實(shí)例方法forwardingTargetForSelector,如果你在自定義類(lèi)中實(shí)現(xiàn)了該方法則可以修改objc_msgSend方法調(diào)用時(shí)的第一個(gè)參數(shù),將方法的調(diào)用者修改成另一個(gè)實(shí)例對(duì)象。例如:
@interface LGStudent : NSObject
- (void)sayInstanceMethod;
@end
@implementation LGStudent
- (void)sayInstanceMethod{
NSLog(@"%s",__func__);
}
@end
@interface LGPerson : NSObject
- (void)sayInstanceMethod;
@end
@implementation LGPerson
- (id)forwardingTargetForSelector:(SEL)sel {
if (sel == @selector(sayInstanceMethod)) {
return [LGStudent alloc];
}
return [super forwardingTargetForSelector:sel];
}
定義LGStudent類(lèi)和LGPerson類(lèi),LGStudent類(lèi)中定義并實(shí)現(xiàn)sayInstanceMethod方法,LGPerson類(lèi)中只定義不實(shí)現(xiàn),然后測(cè)試代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person sayInstanceMethod];
}
return 0;
}
結(jié)束正常打?。?/p>
2020-09-27 19:37:07.166357+0800 002-TestObjc[1924:2860333] -[LGStudent sayInstanceMethod]
Program ended with exit code: 0
慢速轉(zhuǎn)發(fā)
慢速轉(zhuǎn)發(fā)有兩個(gè)關(guān)鍵方法需要配合使用,methodSignatureForSelector和forwardInvocation,還是上面的例子,但把forwardingTargetForSelector修改成下面的實(shí)現(xiàn):
@implementation LGPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.target = [LGStudent alloc];
anInvocation.selector = @selector(sayInstanceMethod);
[anInvocation invoke];
}
結(jié)果任然會(huì)正常輸出,并且在forwardInvocation中什么事情都不處理也不會(huì)報(bào)方法未實(shí)現(xiàn)的異常了,說(shuō)明系統(tǒng)在前面所有流程中都未找到方法實(shí)現(xiàn)的情況下,將sayInstanceMethod的方法調(diào)用完全交給開(kāi)發(fā)者處理了。
怎么來(lái)的
上面的快速轉(zhuǎn)發(fā)和慢速轉(zhuǎn)發(fā)目前確實(shí)能幫助開(kāi)發(fā)者解決方法未實(shí)現(xiàn)的崩潰問(wèn)題,但由于在runtime源碼層面沒(méi)有對(duì)應(yīng)的實(shí)現(xiàn),我們看不到它真正的調(diào)用流程,所以為了進(jìn)一步的分析整個(gè)過(guò)程我們可以通過(guò)下面兩個(gè)步驟。
-
instrumentObjcMessageSends方法日志文件分析 -
堆棧+Hopper工具反編譯流程分析
1.instrumentObjcMessageSends方法,它是runtime源碼中的方法,具體使用方式如下:
//聲明為外部實(shí)現(xiàn)的方法
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
//打開(kāi)日志記錄
instrumentObjcMessageSends(YES);
//調(diào)用oc方法
[person sayHello];
//關(guān)閉日志記錄
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
而在我們電腦的/tmp目錄下將生成一個(gè)msgSends-xxx的文件,其中內(nèi)容如下:
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject forwardingTargetForSelector:
- LGPerson NSObject methodSignatureForSelector:
- LGPerson NSObject methodSignatureForSelector:
- LGPerson NSObject class
+ LGPerson NSObject resolveInstanceMethod:
+ LGPerson NSObject resolveInstanceMethod:
- LGPerson NSObject doesNotRecognizeSelector:
- LGPerson NSObject doesNotRecognizeSelector:
- LGPerson NSObject class
- OS_xpc_serializer OS_xpc_object dealloc
- OS_object NSObject dealloc
...
當(dāng)前這里只能看到動(dòng)態(tài)方法決議以及消息轉(zhuǎn)發(fā)中幾個(gè)關(guān)鍵方法的調(diào)用情況,那具體是誰(shuí)來(lái)調(diào)用它們呢,還需要更進(jìn)一步的分析工具來(lái)分析。
2.堆棧+Hopper工具,首頁(yè)我們?cè)诋?dāng)前類(lèi)中重寫(xiě)forwardingTargetForSelector方法,并在其中打個(gè)斷點(diǎn),bt查看堆棧信息如下:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000100003e14 002-testObjc`-[LGPerson forwardingTargetForSelector:](self=0x00000001006a2b60, _cmd="forwardingTargetForSelector:", aSelector="sayHello") at LGPerson.m:17:52
frame #1: 0x00007fff36910f0a CoreFoundation`___forwarding___ + 226
frame #2: 0x00007fff36910d98 CoreFoundation`__forwarding_prep_0___ + 120
frame #3: 0x0000000100003db0 002-instrumentObjcMessageSends輔助分析`main(argc=1, argv=0x00007ffeefbff420) at main.m:19:9
frame #4: 0x00007fff709c7cc9 libdyld.dylib`start + 1
可以看到forwardingTargetForSelector方法是在CoreFoundation中的___forwarding___方法中調(diào)用的。我們可以用Hopper反編譯工具看一下CoreFoundation中的實(shí)現(xiàn)。先通過(guò)在lldb下輸入image list拿到CoreFoundation的路徑。大概在這個(gè)位置:
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
將其拖到Hopper工具中來(lái),搜索___forwarding___方法,然后查看偽代碼實(shí)現(xiàn):
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
rdi = arg0;
r15 = rdi;
rcx = COND_BYTE_SET(NE);
if (rsi != 0x0) {
r12 = _objc_msgSend_stret;
}
else {
r12 = _objc_msgSend;
}
rax = rcx;
rbx = *(r15 + rax * 0x8);
rcx = *(r15 + rax * 0x8 + 0x8);
var_140 = rcx;
r13 = rax * 0x8;
if ((rbx & 0x1) == 0x0) goto loc_649bb;
loc_6498b:
rcx = *_objc_debug_taggedpointer_obfuscator ^ rbx;
rax = rcx >> 0x1 & 0x7;
if (rax == 0x7) {
rcx = rcx >> 0x4;
rax = (rcx & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_64d48;
loc_649bb:
var_148 = r13;
var_138 = r12;
var_158 = rsi;
rax = object_getClass(rbx);
r12 = rax;
r13 = class_getName(rax);
r14 = @selector(forwardingTargetForSelector:);
if (class_respondsToSelector(r12, r14) == 0x0) goto loc_64a67;
loc_649fc:
r14 = @selector(forwardingTargetForSelector:);
rdi = rbx;
rax = _objc_msgSend(rdi, r14);
if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
loc_64a19:
r12 = var_138;
r13 = var_148;
if ((rax & 0x1) == 0x0) goto loc_64a5b;
loc_64a2b:
rdx = *_objc_debug_taggedpointer_obfuscator ^ rax;
rcx = rdx >> 0x1 & 0x7;
if (rcx == 0x7) {
rcx = (rdx >> 0x4 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_64d45;
loc_64a5b:
*(r15 + r13) = rax;
r15 = 0x0;
goto loc_64d82;
loc_64d82:
if (*___stack_chk_guard == *___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_64d45:
rbx = rax;
goto loc_64d48;
loc_64d48:
if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;
loc_64d55:
rax = _getAtomTarget(rbx);
*(r15 + r13) = rax;
___invoking___(r12, r15);
if (*r15 == rax) {
*r15 = rbx;
}
goto loc_64d82;
loc_64ed1:
____forwarding___.cold.4();
rax = *(rdi + 0x8);
return rax;
loc_64a67:
var_138 = rbx;
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
loc_64a8a:
r14 = var_138;
var_148 = r15;
if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_64dd7;
loc_64ab2:
rax = [r14 methodSignatureForSelector:var_140];
rbx = var_158;
if (rax == 0x0) goto loc_64e3c;
loc_64ad5:
r12 = rax;
rax = [rax _frameDescriptor];
r13 = rax;
if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
rax = sel_getName(var_140);
r8 = "";
rcx = r8;
if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = " not";
}
if (rbx == 0x0) {
r8 = " not";
}
_CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", rax, rcx, r8, r9, stack[2003]);
}
var_150 = r13;
if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_64c19;
loc_64b6c:
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
r15 = [NSInvocation requiredStackSizeForSignature:r12];
rsi = *____forwarding___.invClassSize;
rsp = rsp - ___chkstk_darwin();
r13 = rsp;
__bzero(r13, rsi);
rbx = rsp - ___chkstk_darwin();
objc_constructInstance(*____forwarding___.invClass, r13);
var_140 = r15;
[r13 _initWithMethodSignature:r12 frame:var_148 buffer:rbx size:r15];
[var_138 _forwardStackInvocation:r13];
r14 = 0x1;
goto loc_64c76;
loc_64c76:
if (*(int8_t *)(r13 + 0x34) != 0x0) {
rax = *var_150;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rsi = var_148 + *(int32_t *)(rax + 0x1c);
rdx = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rdx + rsi), *(rdx + *(int32_t *)(rax + 0x1c) + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
}
}
rax = [r12 methodReturnType];
rbx = rax;
rax = *(int8_t *)rax;
if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
r15 = *(r13 + 0x10);
if (r14 != 0x0) {
r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
[r13 release];
rax = *(int8_t *)rbx;
}
if (rax == 0x44) {
asm { fld tword [r15] };
}
}
else {
r15 = ____forwarding___.placeholder;
if (r14 != 0x0) {
[r13 release];
}
}
goto loc_64d82;
loc_64c19:
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;
loc_64c3b:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
[r14 forwardInvocation:rax];
var_140 = 0x0;
r14 = 0x0;
goto loc_64c76;
loc_64ec2:
rdi = &var_130;
____forwarding___.cold.3(rdi, r14);
goto loc_64ed1;
loc_64e3c:
rax = sel_getName(var_140);
r14 = rax;
rax = sel_getUid(rax);
if (rax != var_140) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, rax, r9, stack[2003]);
}
if (class_respondsToSelector(object_getClass(var_138), @selector(doesNotRecognizeSelector:)) == 0x0) {
____forwarding___.cold.2(var_138);
}
_objc_msgSend(var_138, @selector(doesNotRecognizeSelector:));
asm { ud2 };
rax = loc_64ec2(rdi, rsi);
return rax;
loc_64dd7:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(r14);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
}
goto loc_64e3c;
loc_64dc1:
____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
goto loc_64dd7;
}
可以看到其中forwardingTargetForSelector methodSignatureForSelector forwardInvocation doesNotRecognizeSelector 這幾個(gè)方法間的跳轉(zhuǎn)邏輯。這樣也能輔助我們對(duì)整個(gè)objc_msgSend流程的分析。
最后
好了,分析了這么多,最后來(lái)一張整體流程的思維導(dǎo)圖總結(jié)一下吧。
