简介
OC中方法调用就是向指定对象发送消息.
OC是一门动态语言,OC中所有方法的调用以及类的生成都是通过Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现.
消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
消息发送阶段解析
[receiver message]
,这是一个方法调用的格式,clang一下其转化代码:
//OC代码
[self test];
//clang之后
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("test"));
//objc_msgSend
/**
* Sends a message with a simple return value to an instance of a class.
*
* @param self A pointer to the instance of the class that is to receive the message.
* @param op The selector of the method that handles the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method.
*
* @note When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
*/
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
那么objc_msgSend
方法会做些什么工作,在这篇文章中,作者给出一下结论 :
- Check for ignored selectors (GC) and short-circuit.
- Check for nil target.
- If nil & nil receiver handler configured, jump to handler
- If nil & no handler (default), cleanup and return.
- Find the IMP on the class of the target
- If found, jump to it.
- Not found, Perform step 4
- Search the class’s method cache for the method IMP
- If found, jump to it.
- Not found: lookup the method IMP in the class itself
上面流程结束后:
- If found, jump to it.
- If not found, jump to forwarding mechanism.
总结上面的流程 :
- 检测是否忽略
selectors
- 检测
target
是否为空
如果为空,并且有响应的nil处理函数,就跳转到相应的处理中.如果没有nil处理函数,就会自动清理现场并返回,这一点就是为何在OC中给nil
发送消息不会崩溃的原因. - 如果target不为空,在该class的缓存中查找方法对应的IMP实现.
如果找到,就跳转执行
如果没找到,就在方法分发表里面继续查找,一直到NSObject为止,查找过程如下图:
- 如果最后还没有找到,那就进入到消息转发阶段.到这里,消息发送阶段的工作完成,这一阶段主要完成的是通过select()快速查找IMP的过程。
objc_msgSend源码解析
先来看一下objc_msgSend
的伪代码,
//下面方法的逻辑就是获取imp指针并调用方法
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
在runtime源码中,objc_msgSend
的代码是用汇编语言编写的,针对不同架构有不同实现,下面给出一份x86_64
架构下的源码,对应代码可以在objc-msg-x86_64.s中找到
.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
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
NilTest NORMAL
GetIsaFast NORMAL // r10 = self->isa
CacheLookup NORMAL, CALL // calls IMP on success
NilTestReturnZero NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
jmp __objc_msgSend_uncached
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
NilTest NORMAL //判断消息接受者是否为空, 为空则返回
GetIsaFast NORMAL // r10 = self->isa
CacheLookup NORMAL, LOOKUP // returns IMP on success
NilTestReturnIMP NORMAL
GetIsaSupport NORMAL
--------------------------------------------------------------------
//__objc_msgSend_uncached的源码
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup NORMAL // r11 = IMP
jmp *%r11 // goto *imp
END_ENTRY __objc_msgSend_uncached
以LCacheMiss
为分割线,_objc_msgSend
的步骤分为两步: CacheLookup
和 MethodTableLookup
.
下面仔细分析其中的一些过程 :
//NilTest NORMAL
/////////////////////////////////////////////////////////////////////
//
// NilTest return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// %a1 or %a2 (STRET) = receiver
//
// On exit: Loads non-nil receiver in %a1 or %a2 (STRET)
// or returns.
//
/////////////////////////////////////////////////////////////////////
.macro NilTest
.if $0 != STRET
testq %a1, %a1 //testq : 逻辑与操作,判断%a1是否为空
.else
testq %a2, %a2
.endif
PN
jz LNilTestSlow_f //如果为0(空),则跳转LNilTestSlow_f,这个应该就是一个空处理,具体过程是
// NilTest NORMAL -为空-> LNilTestSlow_f --> ZeroReturn
.endmacro
//------ ZeroReturn ------
.macro ZeroReturn
xorl %eax, %eax //按位异或,相同的位置为0,不同的位置为1,eax和eax的每一位都相同,所以相当于清零
xorl %edx, %edx //将寄存器 %edx 设置为 0
xorps %xmm0, %xmm0 //应该也是清空寄存器
xorps %xmm1, %xmm1 //应该也是清空寄存器
.endmacro
%a1 or %a2 (STRET) = receiver
,所以NilTest
是判断消息接受者是否为空,为空的话跳转到空处理ZeroReturn
,因为这个地方对消息接受者为nil的情况做了判断,所以在OC中对nil
发送消息是不会崩溃的.
GetIsaFast宏可以快速地获取到对象的 isa 指针地址(放到 r11 寄存器,r10会被重写;在 arm 架构上是直接赋值到 r9)
//GetIsaFast NORMAL
/////////////////////////////////////////////////////////////////////
//
// GetIsaFast return-type
// GetIsaSupport return-type
//
// Sets r10 = obj->isa. Consults the tagged isa table if necessary.
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// a1 or a2 (STRET) = receiver
//
// On exit: r10 = receiver->isa
// r11 is clobbered
//
/////////////////////////////////////////////////////////////////////
.macro GetIsaFast
.if $0 != STRET
testb $$1, %a1b
PN
jnz LGetIsaSlow_f
movq $$ ISA_MASK, %r10
andq (%a1), %r10
.else
testb $$1, %a2b
PN
jnz LGetIsaSlow_f
movq $$ ISA_MASK, %r10
andq (%a2), %r10
.endif
LGetIsaDone:
.endmacro
CacheLookup
: 在方法缓存列表中寻找IMP指针
/////////////////////////////////////////////////////////////////////
//
// CacheLookup return-type, caller
//
// Locate the implementation for a class in a selector's method cache.
//
// Takes:
// $0 = NORMAL, FPRET, FP2RET, STRET
// $1 = CALL, LOOKUP, GETIMP
// a1 or a2 (STRET) = receiver
// a2 or a3 (STRET) = selector
// r10 = class to search
//
// On exit: r10 clobbered
// (found) calls or returns IMP in r11, eq/ne set for forwarding
// (not found) jumps to LCacheMiss, class still in r10
//
/////////////////////////////////////////////////////////////////////
.macro CacheLookup
.if $0 != STRET
movq %a2, %r11 // r11 = _cmd
.else
movq %a3, %r11 // r11 = _cmd
.endif
andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask
shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4
addq 16(%r10), %r11 // r11 = class->cache.buckets + offset
.if $0 != STRET
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1f // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
1:
// loop
cmpq $$1, cached_sel(%r11)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++
2:
.if $0 != STRET
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
3:
// wrap or miss
jb LCacheMiss_f // if (bucket->sel < 1) cache miss
// wrap
movq cached_imp(%r11), %r11 // bucket->imp is really first bucket
jmp 2f
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
1:
// loop
cmpq $$1, cached_sel(%r11)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++
2:
.if $0 != STRET
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp 找到imp,就调用/返回
3:
// double wrap or miss
jmp LCacheMiss_f
.endmacro
r10中存储类,IMP存储在r11中(不同版本的runtime,存储位置不同),返回时有两种情况 :
- 找到IMP,就调用/返回r11中的IMP
- 未找到,就跳转到
LCacheMiss_f
,进行下一阶段
如果程序跳到LCacheMiss
,就说明未命中缓存。这个时候就要开始下一阶段MethodTableLookup
的查找了。
// ------ __objc_msgSend_uncached ------
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup NORMAL // r11 = IMP
jmp *%r11 // goto *imp
END_ENTRY __objc_msgSend_uncached
//------ MethodTableLookup ------
/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup NORMAL|STRET
//
// Takes: a1 or a2 (STRET) = receiver
// a2 or a3 (STRET) = selector to search for
// r10 = class to search
//
// On exit: imp in %r11, eq/ne set for forwarding
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup
push %rbp
mov %rsp, %rbp
sub $$0x80+8, %rsp // +8 for alignment
movdqa %xmm0, -0x80(%rbp)
push %rax // might be xmm parameter count
movdqa %xmm1, -0x70(%rbp)
push %a1
movdqa %xmm2, -0x60(%rbp)
push %a2
movdqa %xmm3, -0x50(%rbp)
push %a3
movdqa %xmm4, -0x40(%rbp)
push %a4
movdqa %xmm5, -0x30(%rbp)
push %a5
movdqa %xmm6, -0x20(%rbp)
push %a6
movdqa %xmm7, -0x10(%rbp)
// _class_lookupMethodAndLoadCache3(receiver, selector, class)
.if $0 == NORMAL
// receiver already in a1
// selector already in a2
.else
movq %a2, %a1
movq %a3, %a2
.endif
movq %r10, %a3
//上面是准备__class_lookupMethodAndLoadCache3的参数, a1->receiver, a2->selector, r10->class
call __class_lookupMethodAndLoadCache3
// IMP is now in %rax
movq %rax, %r11 //IMP会重r11放到rax中返回
movdqa -0x80(%rbp), %xmm0
pop %a6
movdqa -0x70(%rbp), %xmm1
pop %a5
movdqa -0x60(%rbp), %xmm2
pop %a4
movdqa -0x50(%rbp), %xmm3
pop %a3
movdqa -0x40(%rbp), %xmm4
pop %a2
movdqa -0x30(%rbp), %xmm5
pop %a1
movdqa -0x20(%rbp), %xmm6
pop %rax
movdqa -0x10(%rbp), %xmm7
.if $0 == NORMAL
cmp %r11, %r11 // set eq for nonstret forwarding
.else
test %r11, %r11 // set ne for stret forwarding
.endif
leave
.endmacro
下面来看下__class_lookupMethodAndLoadCache3
方法,在Runtime源码中(objc-runtime-new.mm)可以找到该方法实现:
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. //标准的IMP查找
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
//对debug模式下的assert进行unlock
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
//如果cache参数为YES,会调用cache_getImp方法从缓存中查找IMP
//_class_lookupMethodAndLoadCache3 中cache参数为NO,所以这一步不会执行
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 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();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
//如果initialize参数为YES,并且是第一次用到这个类
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock(); //对runtimeLock加写操作锁
_class_initialize (_class_getNonMetaClass(cls, inst));//初始化类,申请空间
runtimeLock.lock(); //解锁
// 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
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
//在该类的cache中查找
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
//在该类的方法列表中查找
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
//在父类的缓存和方法列表中查找,循环直到superclass为nil,也就是到NSObject
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 如果resolver参数为YES,并且没有尝试过方法解析
// 其实就是调用_class_resolveMethod方法,看看开发者有没有动态增加方法实现
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
//上面注释中说,不会缓存结果,并且没有保持锁定,所以结果有可能改变,所以重复一次上面的过程
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
//如果到这里还没有找到IMP的实现,则会进入消息转发过程,这个地方IMP变成了_objc_msgForward_impcache,并且加入了缓存中,这个是一个标志,如果在查找过程中,IMP==_objc_msgForward_impcache,则会返回nil,否则返回imp,后面的lookUpImpOrNil方法中可以看到
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
在来看下查找方法动态实现的过程 :
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
//如果cls不是元类,则查找实例方法
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//如果是元类,查找类方法
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
//如果lookUpImpOrNil返回nil,就代表在父类中的缓存中找到,于是需要再调用一次_class_resolveInstanceMethod方法。保证给sel添加上了对应的IMP。
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
消息转发阶段(Message Forwarding)
消息转发阶段,会调用id _objc_msgForward(id self, SEL _cmd,...)
方法。在objc-msg-x86_64.s中有其汇编的实现。
/********************************************************************
*
* id _objc_msgForward(id self, SEL _cmd,...);
*
* _objc_msgForward and _objc_msgForward_stret are the externally-callable
* functions returned by things like method_getImplementation().
* _objc_msgForward_impcache is the function pointer actually stored in
* method caches.
*
********************************************************************/
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band condition register is NE for stret, EQ otherwise.
//jne 不等于则跳转
jne __objc_msgForward_stret //不等于__objc_msgForward_impcache,则执行__objc_msgForward_stret
jmp __objc_msgForward //否则执行__objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
//消息转发回调,在objc-runtime.mm中可以找到其实现,具体实现看下面内容
movq __objc_forward_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward
ENTRY __objc_msgForward_stret
// Struct-return version
movq __objc_forward_stret_handler(%rip), %r11
jmp *%r11
END_ENTRY __objc_msgForward_stret
ENTRY _objc_msgSend_debug
jmp _objc_msgSend
END_ENTRY _objc_msgSend_debug
ENTRY _objc_msgSendSuper2_debug
jmp _objc_msgSendSuper2
END_ENTRY _objc_msgSendSuper2_debug
ENTRY _objc_msgSend_stret_debug
jmp _objc_msgSend_stret
END_ENTRY _objc_msgSend_stret_debug
ENTRY _objc_msgSendSuper2_stret_debug
jmp _objc_msgSendSuper2_stret
END_ENTRY _objc_msgSendSuper2_stret_debug
ENTRY _objc_msgSend_fpret_debug
jmp _objc_msgSend_fpret
END_ENTRY _objc_msgSend_fpret_debug
ENTRY _objc_msgSend_fp2ret_debug
jmp _objc_msgSend_fp2ret
END_ENTRY _objc_msgSend_fp2ret_debug
ENTRY _objc_msgSend_noarg
jmp _objc_msgSend
END_ENTRY _objc_msgSend_noarg
ENTRY _method_invoke
movq method_imp(%a2), %r11
movq method_name(%a2), %a2
jmp *%r11
END_ENTRY _method_invoke
ENTRY _method_invoke_stret
movq method_imp(%a3), %r11
movq method_name(%a3), %a3
jmp *%r11
END_ENTRY _method_invoke_stret
.section __DATA,__objc_msg_break
.quad 0
.quad 0
__objc_forward_handler
和_objc_forward_stret_handler
的实现如下:
// Default forward handler halts the process.
__attribute__((noreturn)) 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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret
objc_defaultForwardStretHandler(id self, SEL sel)
{
objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif
#endif
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
#if SUPPORT_STRET
_objc_forward_stret_handler = fwd_stret;
#endif
}
所以,如果给一个对象发送一个消息(没有实现的方法),而且在父类中也没有找到其实现,就会崩溃,报错信息就是unrecognized selector sent to instance
.
objc_setForwardHandler
具体调用栈需要通过逆向的手段,可以参考这个Objective-C 消息发送与转发机制原理,下面给出一段关键代码:
void __forwarding__(BOOL isStret, void *frameStackPointer, ...) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 4);
Class receiverClass = object_getClass(receiver);
//会先判断是否有forwardingTargetForSelector实现
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
const char *className = class_getName(object_getClass(receiver));
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix);
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"-[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
sel_getName(sel),
receiver);
}
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.",
sel_getName(sel),
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;
}
}
}
const char *selName = sel_getName(sel);
SEL *registeredSel = sel_getUid(selName);
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);
} 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);
}
上面代码逻辑如下 :
- 调用
forwardingTargetForSelector
,获取新的target作为receiver,重新进行消息发送阶段,如果没有实现forwardingTargetForSelector
,或者新的target为nil,进行下一步 - 调用
methodSignatureForSelector
获取方法签名,如果签名正确,再调用forwardInvocation
,执行NSInvocation
对象,如果不能响应methodSignatureForSelector
方法,进行下一步 - 未找到转发对象,执行异常处理
下面是整个消息发送与转发流程图,来自Objective-C 消息发送与转发机制原理
汇编知识有待加强,逆向也要搞一下,路还很长