[toc]
參考
http://www.reibang.com/p/1bde36ad9938
objc_msgSend() 簡介
方法調(diào)用本質(zhì)
OC中的方法調(diào)用, 其實(shí)都是轉(zhuǎn)換為 objc_msgSend() 函數(shù)的調(diào)用;
消息機(jī)制: 給方法調(diào)用者發(fā)送消息
Person *person = [[Person alloc] init];
// objc_msgSend(person, @selector(personTest));
// 消息接收者(receiver):person
// 消息名稱:personTest
[person personTest];
// objc_msgSend([Person class], @selector(initialize));
// 消息接收者(receiver):[Person class]
// 消息名稱:initialize
[Person initialize];
流程
objc_msgSend 的執(zhí)行流程可以分為3大階段
消息發(fā)送
動態(tài)方法解析
消息轉(zhuǎn)發(fā)
補(bǔ)救方案
首先, 調(diào)用方法時, 系統(tǒng)會查看這個對象能否接收這個消息 (查看這個類有沒有這個方法, 或者有沒有實(shí)現(xiàn)這個方法。)
如果不能, 就會調(diào)用下面這幾個方法, 給你“補(bǔ)救”的機(jī)會, 你可以先理解為幾套防止程序crash的備選方案, 我們就是利用這幾個方案進(jìn)行消息轉(zhuǎn)發(fā);
注意, 前一套方案實(shí)現(xiàn), 后一套方法就不會執(zhí)行伏尼。
如果這幾套方案你都沒有做處理, 那么 objc_msgSend() 最后找不到合適的方法進(jìn)行調(diào)用, 會報錯 unrecognized selector sent to instance
方案1 (動態(tài)方法解析, 添加方法):
// 首先烦粒,系統(tǒng)會調(diào)用resolveInstanceMethod或resolveClassMethod 讓你自己為這個方法動態(tài)增加實(shí)現(xiàn)扰她。動態(tài)添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
方案2 (消息轉(zhuǎn)發(fā)):
// 如果不對resolveInstanceMethod做任何處理, 系統(tǒng)會來到方案二, 走forwardingTargetForSelector方法徒役,我們可以返回其他實(shí)例對象, 實(shí)現(xiàn)消息轉(zhuǎn)發(fā)忧勿。
- (id)forwardingTargetForSelector:(SEL)aSelector;
方案3 (消息轉(zhuǎn)發(fā)調(diào)用):
// 如果不實(shí)現(xiàn) forwardingTargetForSelector, 系統(tǒng)就會調(diào)用方案三的兩個方法 方法簽名&轉(zhuǎn)發(fā)調(diào)用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
流程圖示
消息發(fā)送 ??
查找方法的執(zhí)行流程
圖示
源碼解讀
objc_msgSend()
因?yàn)檫@個方法調(diào)用頻次太高, 所以使用匯編實(shí)現(xiàn)提高效率
// objc-msg-arm64.s
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// p0寄存器, 存放的是消息接收者 receiver, 判斷是否為0
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
// b是跳轉(zhuǎn); 如果 le (p0≤0)成立, 則跳轉(zhuǎn)到 LNilOrTagged
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
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
// 消息接收者為空, 則直接退出函數(shù)
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
CacheLookup
查找方法緩存
// 宏定義
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
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
CheckMiss
緩存沒有命中
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL // _objc_msgSend 調(diào)用了 CacheLookup 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
__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
__objc_msgLookup_uncached
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
_cache_getImp
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0, #0
ret
END_ENTRY _cache_getImp
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
// 匯編里面 b 開頭的指令一般是跳轉(zhuǎn)調(diào)用
// c函數(shù)在編譯成匯編之后, 會多一條下劃線, 找到c的 lookUpImpOrForward, 返回的是函數(shù)地址 IMP ★
// 傳入的參數(shù) behavior = LOOKUP_INITIALIZE | LOOKUP_RESOLVER = 0b0001 | 0b0010 = 0b0011
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
lookUpImpOrForward()
★
MethodTableLookup 和 resolveMethod_locked() 調(diào)用
// objc-runtime-new.mm
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
// 消息轉(zhuǎn)發(fā)
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup 樂觀(嘗試)查找一下緩存, 因?yàn)檫@期間有可能別的地方有調(diào)用了這個方法, 添加到了緩存
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
//
// TODO: this check is quite costly during process startup.
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
runtimeLock.assertLocked();
// 遍歷用的`游標(biāo)`(類/元類)
curClass = cls;
// The code used to lookup the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
// 如果當(dāng)前類的緩存中沒有, 則遍歷, 根據(jù)superclass指針向上一層層查找 ★★
for (unsigned attempts = unreasonableClassCount();;) {
// 根據(jù)方法名 sel 去當(dāng)前`游標(biāo)`中查找方法 ★
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel); // ★
if (meth) {
// 取出函數(shù)地址 IMP
imp = meth->imp;
goto done;
}
// 游標(biāo)向父類步進(jìn), 如果直到根類都沒找到, 進(jìn)入消息轉(zhuǎn)發(fā) ★
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.
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)) {
// 從父類中找到了方法, 填充到當(dāng)前類的緩存 ★★
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 首次查找時, 沒找到, 嘗試一次動態(tài)方法解析 ★★★
// 此時, behavior = 0b0011; &= LOOKUP_RESOLVER 為 0b0010, 可以進(jìn)入if();
// 由于 resolveMethod_locked 內(nèi)部調(diào)用了本函數(shù), 而如果 resolveMethod_locked 并沒有動態(tài)添加方法, 會再次來到這里
// 此時, behavior = 0b0101; &= LOOKUP_RESOLVER 為 0b0000, 不能進(jìn)入if();
if (slowpath(behavior & LOOKUP_RESOLVER)) {
// behavior = 0b0011 ^ 0b0010 = 0b0001
behavior ^= LOOKUP_RESOLVER;
// 該函數(shù)內(nèi)部會回調(diào)本函數(shù), behavior = behavior | LOOKUP_CACHE = 0b0101 ★
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;
}
enum {
LOOKUP_INITIALIZE = 1, // 0b0001
LOOKUP_RESOLVER = 2, // 0b0010
LOOKUP_CACHE = 4, // 0b0100
LOOKUP_NIL = 8, // 0b1000
};
getMethodNoSuper_nolock()
根據(jù)方法名 sel 去 類/元類 中查找方法
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) {
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
// cls->data() 即 bits & FAST_DATA_MASK 可以獲取到 struct class_rw_t, 然后從中拿到 methods (二維數(shù)組) ★
auto const methods = cls->data()->methods();
// 遍歷二維數(shù)組, 找到 method_t
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;
}
search_method_list_inline()
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;
}
findMethodInSortedMethodList()
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) {
// `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)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
log_and_fill_cache()
填充緩存 - 在 lookUpImpOrForward 中被調(diào)用
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver); // ★
}
cache_fill()
填充緩存
void cache_fill(Class cls, SEL sel, IMP imp, id receiver) {
runtimeLock.assertLocked();
#if !DEBUG_TASK_THREADS
// Never cache before +initialize is done
if (cls->isInitialized()) {
cache_t *cache = getCache(cls);
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
// 調(diào)用 cache_t::insert 將新調(diào)用的方法插入到當(dāng)前類對象的方法緩存中 ★
cache->insert(cls, sel, imp, receiver);
}
#else
_collecting_in_critical();
#endif
}
動態(tài)方法解析 ??
圖示
源碼解讀
resolveMethod_locked()
在 lookUpImpOrForward() 中有調(diào)用
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) { // 類對象, 實(shí)例方法 ★
// 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);
}
}
// 再次調(diào)用 `lookUpImpOrForward()` 入?yún)?behavior = 0b0001 | 0b0100 = 0b0101
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
resolveInstanceMethod()
/
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
*/
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.
return;
}
// 給傳入的 cls 發(fā)送 `resolveInstanceMethod:` ★
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 這個返回值只是做了一些打印, 所以 `resolveInstanceMethod:` 的返回值為 YES / NO 實(shí)際效果都一樣
// `resolveInstanceMethod:` 不添加方法實(shí)現(xiàn), 僅僅返回 YES 是沒用的 ★
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
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()
/*
* resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
*/
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);
}
}
// 讓元類調(diào)用 `resolveClassMethod:`
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));
}
}
}
動態(tài)添加方法
開發(fā)者可以實(shí)現(xiàn)以下方法, 來動態(tài)添加方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
示例
// c函數(shù)名就是函數(shù)地址
void c_other(id self, SEL _cmd) {
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 動態(tài)添加test方法的實(shí)現(xiàn), 將方法添加到 class_rw_t 的 methods 中 ★
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test)) {
// 第一個參數(shù)是object_getClass(self) 元類對象
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
動態(tài)解析過后, 會重新走“消息發(fā)送”的流程
<u>“從receiverClass 的 cache中查找方法”這一步開始執(zhí)行</u>
消息轉(zhuǎn)發(fā) ??
自己及父類都無法處理該消息, 也沒有動態(tài)方法解析, 會進(jìn)入消息轉(zhuǎn)發(fā)階段, 將消息轉(zhuǎn)發(fā)給其他實(shí)例 /類 (備用接收者)
注意, 消息機(jī)制支持, 類方法, 實(shí)例方法和類方法本質(zhì)沒有區(qū)別, 都是消息機(jī)制谆膳。
當(dāng)對象不能接受某個selector時, 如果不對 resolveInstanceMethod 做任何處理, 系統(tǒng)會來到 forwardingTargetForSelector 方法, 我們可以返回其他實(shí)例對象, 實(shí)現(xiàn)消息轉(zhuǎn)發(fā)。
圖示
使用
forwardingTargetForSelector
// 對于類方法的轉(zhuǎn)發(fā)
+ (id)forwardingTargetForSelector:(SEL)aSelector {
// 這里甚至可以返回實(shí)例對象, 轉(zhuǎn)發(fā)給實(shí)例對象來調(diào)用, 相當(dāng)于:
// objc_msgSend([[Cat alloc] init], @selector(test))
// [[[Cat alloc] init] test]
if (aSelector == @selector(test)) return [[Cat alloc] init];
return [super forwardingTargetForSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// 返回一個實(shí)現(xiàn)了該方法的對象, 實(shí)際相當(dāng)于調(diào)用了下面的方法
// `objc_msgSend([[Animal alloc] init], aSelector)`
return [[Animal alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 若返回的對象如果沒有實(shí)現(xiàn)該方法, 相當(dāng)于返回了nil
若實(shí)現(xiàn)了該方法, 且返回值不空, 則將消息轉(zhuǎn)發(fā)給其他對象
若未實(shí)現(xiàn)該方法, 或者返回了nil, 會調(diào)用
methodSignatureForSelector:
, 要求返回方法簽名
methodSignatureForSelector
// 方法簽名:返回值類型、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// 手寫方法簽名
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
// 也可以讓一個實(shí)現(xiàn)了該方法的對象, 調(diào)用本方法, 并返回結(jié)果
// return [[[Xxx alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
-
若實(shí)現(xiàn)了該方法, 且返回值不空, 則將其返回的方法簽名封裝到
NSInvocation
,并調(diào)用forwardInvocation:
;其中, 方法簽名決定了 NSInvocation 方法的返回值及參數(shù)數(shù)量、類型晃危。
若未實(shí)現(xiàn)該方法, 或者返回了nil, 會調(diào)用
doesNotRecognizeSelector:
, 直接報找不到方法; ★
forwardInvocation
/*
* NSInvocation 封裝了一個方法調(diào)用, 包括: 方法調(diào)用者僚饭、方法名胧砰、方法參數(shù)
* anInvocation.target 方法調(diào)用者, 默認(rèn)是最開始接收消息的對象
* anInvocation.selector 方法名
* [anInvocation getArgument:NULL atIndex:0] 調(diào)用時傳入的參數(shù)
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// anInvocation.target = [[Cat alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[Cat alloc] init]];
}
該方法內(nèi), 不是必須調(diào)用 invoke,
實(shí)際上這個方法內(nèi)部是可以做任何事, 甚至不做任何事;
比如可以修改參數(shù), 修改返回值等等;
NSMethodSignature 顧名思義應(yīng)該就是“方法簽名”, 類似于C++中的編譯器時的函數(shù)簽名偿乖。蘋果官方定義該類為對方法的參數(shù)哲嘲、返回類似進(jìn)行封裝, 協(xié)同NSInvocation實(shí)現(xiàn)消息轉(zhuǎn)發(fā)眠副。通過消息轉(zhuǎn)發(fā)實(shí)現(xiàn)類似C++中的多重繼承囱怕。
iOS中的SEL, 它的作用和C光涂、C++中的函數(shù)指針很相似, 通過performSelector:withObject:函數(shù)可以直接調(diào)用這個消息。
但是perform相關(guān)的這些函數(shù), 有一個局限性, 其參數(shù)數(shù)量不能超過2個, 否則要做很麻煩的處理, 與之相對, NSInvocation也是一種消息調(diào)用的方法, 并且它的參數(shù)沒有限制钝计。這兩種直接調(diào)用對象消息的方法, 在IOS4.0之后, 大多被block結(jié)構(gòu)所取代, 只有在很老的兼容性系統(tǒng)中才會使用私恬。
源碼解讀
__objc_msgForward_impcache
lookUpImpOrForward() 中調(diào)用
// objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
__objc_msgForward
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
_objc_forward_handler
// objc-runtime.mm
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);
}
這里沒有開源, 只有寫打印信息; 接下來就需要逆向分析匯編代碼了;
先考慮換一種思路, 如果不實(shí)現(xiàn)消息轉(zhuǎn)發(fā), Xcode崩潰會輸出函數(shù)調(diào)用棧:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1004a2f50'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2ee22be7 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff67bfa5bf objc_exception_throw + 48
2 CoreFoundation 0x00007fff2eea1c77 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff2ed8744b ___forwarding___ + 1427
4 CoreFoundation 0x00007fff2ed86e28 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff68da1cc9 start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
__forwarding__
其中, ___forwarding___
會調(diào)用 forwardingTargetForSelector:
// 國外開發(fā)者根據(jù)匯編寫出的偽代碼
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 調(diào)用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸對象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// 如果 `forwardingTargetForSelector:` 沒有實(shí)現(xiàn), 或者返回了nil; 會調(diào)用 `methodSignatureForSelector:`, 要求返回方法簽名
// 調(diào)用 methodSignatureForSelector 獲取方法簽名后, 再調(diào)用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** 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.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已經(jīng)在 Runtime 注冊過
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector ★
else if (class_respondsToSelector(receiverClass, @selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
消息轉(zhuǎn)發(fā)的應(yīng)用
- 容災(zāi)處理: 防止找不到方法, 產(chǎn)生崩潰
@implementation Person
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 本來能調(diào)用的方法
if ([self respondsToSelector:aSelector]) {
return [super methodSignatureForSelector:aSelector];
}
// 找不到的方法
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 找不到的方法, 都會來到這里
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 可以在這里收集找不到的方法
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
@end
- NSProxy 專門用來做消息轉(zhuǎn)發(fā)