动态方法决议
在上文中本类和父类中都没找到方法时候,会在报错前调用resolveMethod_locked
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
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
进行检测是否在类中调用了resolveInstanceMethod
或者类方法的resolveClassMethod
根据isa
走位图

+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ ",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayHello));
Method sayMethod = class_getInstanceMethod(self, @selector(sayHello));
return class_addMethod(self, sel, imp, method_getTypeEncoding(sayMethod));
}else if (sel == @selector(sayNB)) {
NSLog(@"%@ ",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(classMethod));
Method sayMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(classMethod));
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, method_getTypeEncoding(sayMethod));
}
return NO;
}
因为NSObject
为根元类,在方法中以及实现了resolveInstanceMethod
,所以这里要return NO
.我们可以在NSObject
中添加分类处理所有的方法切面.
但在写在这里封装成SDK,如果其他人在变成的时候同时也处理了,会导致自己写的浪费.我以我们一般不处理.
快速转发
我们在找到IMP时候
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
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);
}
static int objcMsgLogFD = -1;
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
extern
:该变量或函数定义在别的文件中,先不会报错
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person say666];
instrumentObjcMessageSends(NO);
// [LGPerson performSelector:@selector(sayNB)];
}
return 0;
}
我们通过instrumentObjcMessageSends
打开开关可以打印信息查看,运行之后我们就可以前往/tmp/
文件夹中查看到多了一个msgSends -xxxx
文件,打开
+ 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
说明我们之后会执行forwardingTargetForSelector
,我们在LGPerson中添加该方法,把他指向LGStudent
,就会在LGStudent
中查找该方法,或者在该方法中runtime
给自己添加方法,return self
,这就是快速转发
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%@ ",NSStringFromSelector(aSelector));
return [LGStudent alloc];
}
慢速转发
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s--%@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s--%@",__func__,anInvocation);
anInvocation.target = [LGStudent alloc];
[anInvocation invoke];
}
anInvocation
就是接受的事务,可以在这里更改接受者和调用的方法,在通过[anInvocation invoke]
激活事务

查看消息转发
我们回到最初的错误代码
[person say666];
因为没有实现代码,运行到这里会发生报错,我们通过lldb 打印bt(堆栈信息)
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff734347fa libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x000000010034b719 libsystem_pthread.dylib`pthread_kill + 432
frame #2: 0x00007fff733bba1c libsystem_c.dylib`abort + 120
frame #3: 0x00007fff70455be8 libc++abi.dylib`abort_message + 231
frame #4: 0x00007fff70455d9c libc++abi.dylib`demangling_terminate_handler() + 262
frame #5: 0x00007fff71f8178a libobjc.A.dylib`_objc_terminate() + 96
frame #6: 0x00007fff70462dc7 libc++abi.dylib`std::__terminate(void (*)()) + 8
frame #7: 0x00007fff70462b6c libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
frame #8: 0x00007fff7045445d libc++abi.dylib`__cxa_throw + 113
frame #9: 0x00007fff71f7f933 libobjc.A.dylib`objc_exception_throw + 350
frame #10: 0x00007fff3bd44b61 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
frame #11: 0x00007fff3bc29adf CoreFoundation`___forwarding___ + 1427
frame #12: 0x00007fff3bc294b8 CoreFoundation`__forwarding_prep_0___ + 120
* frame #13: 0x0000000100000cc0 struct`main(argc=1, argv=0x00007ffeefbff480) at main.m:32:9
frame #14: 0x00007fff732ed7fd libdyld.dylib`start + 1
我们看到报错前还有两个方法:___forwarding___
和__forwarding_prep_0___
,我们点击左边的__forwarding_prep_0___
,我们发现这个方法来自CoreFoundation
, 但是我们
CoreFoundation
并未开源,我们只能通过反汇编来查看.
这里反汇编用的是Hopper Disassembler
,在lldb中打印 image list
打印所有堆栈.我们找到CoreFoundation
来自/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
我们把这个文件拖入Hopper Disassembler
,查找__forwarding_prep_0___
,查看伪代码
int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
*(rsp + 0xa0) = zero_extend_64(xmm7);
*(rsp + 0x90) = zero_extend_64(xmm6);
*(rsp + 0x80) = zero_extend_64(xmm5);
*(rsp + 0x70) = zero_extend_64(xmm4);
*(rsp + 0x60) = zero_extend_64(xmm3);
*(rsp + 0x50) = zero_extend_64(xmm2);
*(rsp + 0x40) = zero_extend_64(xmm1);
*(rsp + 0x30) = zero_extend_64(xmm0);
stack[2021] = arg0;
rax = ____forwarding___(rsp, 0x0);
if (rax != 0x0) {
rax = *rax;
}
else {
rax = objc_msgSend(stack[2021], stack[2021]);
}
return rax;
}
双击____forwarding___
继续查看
r14 = @selector(forwardingTargetForSelector:);
if (class_respondsToSelector(r12, r14) == 0x0) goto loc_6459b;
loc_6459b:
var_138 = rbx;
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_648f5;
loc_6490b:
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_64970;
loc_648f5:
____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
goto loc_6490b;
如果一直没有找到,就会发出报错信息
loc_645e6:
rax = [r14 methodSignatureForSelector:var_140];
rbx = var_158;
if (rax == 0x0) goto loc_64970;
loc_6476f:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
[r14 forwardInvocation:rax];
var_140 = 0x0;
r14 = 0x0;
goto loc_647aa;
最后直接调用forwardInvocation
我们可以通过查看汇编和lldb
打印 register read
读取寄存器和Hopper Disassembler
对比来一步步对比排除,除了Hopper Disassembler
,也可以通过IDA
查看
网友评论