[toc]
参考
http://www.jianshu.com/p/1bde36ad9938
objc_msgSend() 简介
方法调用本质
OC中的方法调用, 其实都是转换为 objc_msgSend() 函数的调用;
消息机制: 给方法调用者发送消息
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 的执行流程可以分为3大阶段
-
消息发送
-
动态方法解析
-
消息转发
补救方案
首先, 调用方法时, 系统会查看这个对象能否接收这个消息 (查看这个类有没有这个方法, 或者有没有实现这个方法。)
如果不能, 就会调用下面这几个方法, 给你“补救”的机会, 你可以先理解为几套防止程序crash的备选方案, 我们就是利用这几个方案进行消息转发;
注意, 前一套方案实现, 后一套方法就不会执行。
如果这几套方案你都没有做处理, 那么 objc_msgSend() 最后找不到合适的方法进行调用, 会报错 unrecognized selector sent to instance
方案1 (动态方法解析, 添加方法):
// 首先,系统会调用resolveInstanceMethod或resolveClassMethod 让你自己为这个方法动态增加实现。动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
方案2 (消息转发):
// 如果不对resolveInstanceMethod做任何处理, 系统会来到方案二, 走forwardingTargetForSelector方法,我们可以返回其他实例对象, 实现消息转发。
- (id)forwardingTargetForSelector:(SEL)aSelector;
方案3 (消息转发调用):
// 如果不实现 forwardingTargetForSelector, 系统就会调用方案三的两个方法 方法签名&转发调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
流程图示
image消息发送 😳
查找方法的执行流程
图示
image源码解读
objc_msgSend()
因为这个方法调用频次太高, 所以使用汇编实现提高效率
// 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是跳转; 如果 le (p0≤0)成立, 则跳转到 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
// 消息接收者为空, 则直接退出函数
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 调用了 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 开头的指令一般是跳转调用
// c函数在编译成汇编之后, 会多一条下划线, 找到c的 lookUpImpOrForward, 返回的是函数地址 IMP ★
// 传入的参数 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() 调用
// objc-runtime-new.mm
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 乐观(尝试)查找一下缓存, 因为这期间有可能别的地方有调用了这个方法, 添加到了缓存
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();
// 遍历用的`游标`(类/元类)
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().
// 如果当前类的缓存中没有, 则遍历, 根据superclass指针向上一层层查找 ★★
for (unsigned attempts = unreasonableClassCount();;) {
// 根据方法名 sel 去当前`游标`中查找方法 ★
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel); // ★
if (meth) {
// 取出函数地址 IMP
imp = meth->imp;
goto done;
}
// 游标向父类步进, 如果直到根类都没找到, 进入消息转发 ★
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)) {
// 从父类中找到了方法, 填充到当前类的缓存 ★★
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 首次查找时, 没找到, 尝试一次动态方法解析 ★★★
// 此时, behavior = 0b0011; &= LOOKUP_RESOLVER 为 0b0010, 可以进入if();
// 由于 resolveMethod_locked 内部调用了本函数, 而如果 resolveMethod_locked 并没有动态添加方法, 会再次来到这里
// 此时, behavior = 0b0101; &= LOOKUP_RESOLVER 为 0b0000, 不能进入if();
if (slowpath(behavior & LOOKUP_RESOLVER)) {
// behavior = 0b0011 ^ 0b0010 = 0b0001
behavior ^= LOOKUP_RESOLVER;
// 该函数内部会回调本函数, 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()
根据方法名 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 (二维数组) ★
auto const methods = cls->data()->methods();
// 遍历二维数组, 找到 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 中被调用
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
// 调用 cache_t::insert 将新调用的方法插入到当前类对象的方法缓存中 ★
cache->insert(cls, sel, imp, receiver);
}
#else
_collecting_in_critical();
#endif
}
动态方法解析 😳
图示
image源码解读
resolveMethod_locked()
在 lookUpImpOrForward() 中有调用
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);
}
}
// 再次调用 `lookUpImpOrForward()` 入参 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 发送 `resolveInstanceMethod:` ★
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 这个返回值只是做了一些打印, 所以 `resolveInstanceMethod:` 的返回值为 YES / NO 实际效果都一样
// `resolveInstanceMethod:` 不添加方法实现, 仅仅返回 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);
}
}
// 让元类调用 `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));
}
}
}
动态添加方法
开发者可以实现以下方法, 来动态添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
示例
// c函数名就是函数地址
void c_other(id self, SEL _cmd) {
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 动态添加test方法的实现, 将方法添加到 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)) {
// 第一个参数是object_getClass(self) 元类对象
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
动态解析过后, 会重新走“消息发送”的流程
<u>“从receiverClass 的 cache中查找方法”这一步开始执行</u>
消息转发 😳
自己及父类都无法处理该消息, 也没有动态方法解析, 会进入消息转发阶段, 将消息转发给其他实例 /类 (备用接收者)
注意, 消息机制支持, 类方法, 实例方法和类方法本质没有区别, 都是消息机制。
当对象不能接受某个selector时, 如果不对 resolveInstanceMethod 做任何处理, 系统会来到 forwardingTargetForSelector 方法, 我们可以返回其他实例对象, 实现消息转发。
图示
image使用
forwardingTargetForSelector
// 对于类方法的转发
+ (id)forwardingTargetForSelector:(SEL)aSelector {
// 这里甚至可以返回实例对象, 转发给实例对象来调用, 相当于:
// 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)) {
// 返回一个实现了该方法的对象, 实际相当于调用了下面的方法
// `objc_msgSend([[Animal alloc] init], aSelector)`
return [[Animal alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 若返回的对象如果没有实现该方法, 相当于返回了nil
-
若实现了该方法, 且返回值不空, 则将消息转发给其他对象
-
若未实现该方法, 或者返回了nil, 会调用
methodSignatureForSelector:
, 要求返回方法签名
methodSignatureForSelector
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// 手写方法签名
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
// 也可以让一个实现了该方法的对象, 调用本方法, 并返回结果
// return [[[Xxx alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
-
若实现了该方法, 且返回值不空, 则将其返回的方法签名封装到
NSInvocation
,并调用forwardInvocation:
;其中, 方法签名决定了 NSInvocation 方法的返回值及参数数量、类型。
-
若未实现该方法, 或者返回了nil, 会调用
doesNotRecognizeSelector:
, 直接报找不到方法; ★
forwardInvocation
/*
* NSInvocation 封装了一个方法调用, 包括: 方法调用者、方法名、方法参数
* anInvocation.target 方法调用者, 默认是最开始接收消息的对象
* anInvocation.selector 方法名
* [anInvocation getArgument:NULL atIndex:0] 调用时传入的参数
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// anInvocation.target = [[Cat alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[Cat alloc] init]];
}
-
该方法内, 不是必须调用 invoke,
-
实际上这个方法内部是可以做任何事, 甚至不做任何事;
比如可以修改参数, 修改返回值等等;
NSMethodSignature 顾名思义应该就是“方法签名”, 类似于C++中的编译器时的函数签名。苹果官方定义该类为对方法的参数、返回类似进行封装, 协同NSInvocation实现消息转发。通过消息转发实现类似C++中的多重继承。
iOS中的SEL, 它的作用和C、C++中的函数指针很相似, 通过performSelector:withObject:函数可以直接调用这个消息。
但是perform相关的这些函数, 有一个局限性, 其参数数量不能超过2个, 否则要做很麻烦的处理, 与之相对, NSInvocation也是一种消息调用的方法, 并且它的参数没有限制。这两种直接调用对象消息的方法, 在IOS4.0之后, 大多被block结构所取代, 只有在很老的兼容性系统中才会使用。
源码解读
__objc_msgForward_impcache
lookUpImpOrForward() 中调用
// 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);
}
这里没有开源, 只有写打印信息; 接下来就需要逆向分析汇编代码了;
先考虑换一种思路, 如果不实现消息转发, Xcode崩溃会输出函数调用栈:
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___
会调用 forwardingTargetForSelector:
// 国外开发者根据汇编写出的伪代码
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);
// 调用 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:` 没有实现, 或者返回了nil; 会调用 `methodSignatureForSelector:`, 要求返回方法签名
// 调用 methodSignatureForSelector 获取方法签名后, 再调用 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 是否已经在 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);
}
消息转发的应用
- 容灾处理: 防止找不到方法, 产生崩溃
@implementation Person
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 本来能调用的方法
if ([self respondsToSelector:aSelector]) {
return [super methodSignatureForSelector:aSelector];
}
// 找不到的方法
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 找不到的方法, 都会来到这里
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 可以在这里收集找不到的方法
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
@end
- NSProxy 专门用来做消息转发
网友评论