oc的方法调用
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
- 通过
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
命令将目标文件转换为.cpp文件也可以看出方法调用的本质就是objc_msgSend(receiver,sel)
objc_msgSend缓存快速查找流程
- 通过源码查找objc_msgSend的实现,全局搜索objc_msgSend并没有找到它的实现,在.s(汇编代码)中找到了_objc_msgSend,所以objc_msgSend的底层是用汇编实现的,用汇编有更快的处理速度及参数不确定性影响的优势。
汇编的执行效率大于c/c++,oc方法调用是比较频繁的所以底层用汇编实现提高性能
-
objc_msgSend的cache调用流程图如下
cache方法快速查找流程-2.png
objc_msgSend慢速方法列表查找
- 在上文查找到MethodTableLookup,继续执行到
bl _lookUpImpOrForward
- 继续查找
_lookUpImpOrForward
,发现找不到_lookUpImpOrForward,因为_lookUpImpOrForward不是通过汇编实现的,可以去掉前面的下划线查找,或者通过汇编断点
image.png -
objc_msgSend慢速方法列表查找流程图如下
慢速查找.png - 我们发现如果没有实现动态方法解析及后面的消息转发,
imp=forward_imp = (IMP)_objc_msgForward_impcache
全局搜索找到其汇编实现
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
- 搜索
_objc_forward_handler
找到其实现代码
// 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);
}
- 这个就是我们平常在方法找不到的时候报的
unrecognized selector
错误
resolveInstanceMethod动态方法决议
- oc在调用方法时发现方法没有实现,会再给我们3次机会,第一次就是动态方法决议,后面两次机会就是后面的消息转发的快速转发和慢速转发
- 实现resolveInstanceMethod方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@来了",NSStringFromSelector(sel));
// if (sel == @selector(run)) {
//
// return class_addMethod(self, sel, class_getMethodImplementation(self, @selector(test1)), "v@:");;
// }
return [super resolveInstanceMethod:sel];
}
- 发现打印了2次run来了,通过断点调试打印函数调用堆栈,发现第一次调用堆栈如下
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 12.1
* frame #0: 0x00000001003b8191 libobjc.A.dylib`resolveInstanceMethod(inst=0x000000010112dfc0, sel="run", cls=Person) at objc-runtime-new.mm:6000:12
frame #1: 0x00000001003a3b10 libobjc.A.dylib`resolveMethod_locked(inst=0x000000010112dfc0, sel="run", cls=Person, behavior=1) at objc-runtime-new.mm:6043:9
frame #2: 0x00000001003a3423 libobjc.A.dylib`::lookUpImpOrForward(inst=0x000000010112dfc0, sel="run", cls=Person, behavior=1) at objc-runtime-new.mm:6192:16
frame #3: 0x000000010037ea99 libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1101
frame #4: 0x00000001000018f3 Test`main(argc=1, argv=0x00007ffeefbff588) at main.mm:195:9 [opt]
frame #5: 0x00007fff68db27fd libdyld.dylib`start + 1
frame #6: 0x00007fff68db27fd libdyld.dylib`start + 1
- 第二次调用堆栈
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 12.1
* frame #0: 0x00000001003b8191 libobjc.A.dylib`resolveInstanceMethod(inst=0x0000000000000000, sel="run", cls=Person) at objc-runtime-new.mm:6000:12
frame #1: 0x00000001003a3b10 libobjc.A.dylib`resolveMethod_locked(inst=0x0000000000000000, sel="run", cls=Person, behavior=0) at objc-runtime-new.mm:6043:9
frame #2: 0x00000001003a3423 libobjc.A.dylib`::lookUpImpOrForward(inst=0x0000000000000000, sel="run", cls=Person, behavior=0) at objc-runtime-new.mm:6192:16
frame #3: 0x000000010037d559 libobjc.A.dylib`::class_getInstanceMethod(cls=Person, sel="run") at objc-runtime-new.mm:5922:5
frame #4: 0x00007fff3174f7d6 CoreFoundation`__methodDescriptionForSelector + 282
frame #5: 0x00007fff3176b5a0 CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 38
frame #6: 0x00007fff317376e4 CoreFoundation`___forwarding___ + 408
frame #7: 0x00007fff317374b8 CoreFoundation`__forwarding_prep_0___ + 120
frame #8: 0x00000001000018f3 Test`main(argc=1, argv=0x00007ffeefbff588) at main.mm:195:9 [opt]
frame #9: 0x00007fff68db27fd libdyld.dylib`start + 1
frame #10: 0x00007fff68db27fd libdyld.dylib`start + 1
- 在第一次动态方法解析之后,会进行消息转发,消息转发又分为快速转发
forwardingTargetForSelector
和慢速转发methodSignatureForSelector``forwardInvocation
如果快速转发没有处理,就会进入慢速转发的methodSignatureForSelector
,处理完之后会再次进入lookUpImpOrForward
消息转发
- 上文提到的动态方法决议没有处理之后,报错如下
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person run]: unrecognized selector sent to instance 0x1006584a0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff317d38ab __exceptionPreprocess + 250
1 libobjc.A.dylib 0x000000010038e050 objc_exception_throw + 48
2 CoreFoundation 0x00007fff31852b61 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff31737adf ___forwarding___ + 1427
4 CoreFoundation 0x00007fff317374b8 _CF_forwarding_prep_0 + 120
5 Test 0x00000001000018f3 main + 67
6 libdyld.dylib 0x00007fff68db27fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
- 发现报错的位置在
CoreFoundation
,并不在我们当前的libobjc源码环境下,我们该怎么继续往下分析呢- 使用instrumentObjcMessageSends方法打印日志
- 通过反编译工具(hopper/IDA)查看
CoreFoundation
instrumentObjcMessageSends方法的使用
- 首先通过
lookUpImpOrForward->log_and_fill_cache->logMessageSend
找到logMessageSend
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
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;
}
- 执行logMessageSend需要满足objcMsgLogEnabled为YES,于是就有下面代码打印方法执行的日志,需要注意的是使用ios项目并不会在
/tmp/
文件夹下生成msgSends-%d
的日志文件
image.png - 找到对应的msgSends-xx文件
+ XQPerson NSObject resolveInstanceMethod:
+ XQPerson NSObject resolveInstanceMethod:
- XQPerson NSObject forwardingTargetForSelector:
- XQPerson NSObject forwardingTargetForSelector:
- XQPerson NSObject methodSignatureForSelector:
- XQPerson NSObject methodSignatureForSelector:
- XQPerson NSObject class
+ XQPerson NSObject resolveInstanceMethod:
+ XQPerson NSObject resolveInstanceMethod:
- XQPerson NSObject doesNotRecognizeSelector:
- XQPerson NSObject doesNotRecognizeSelector:
- XQPerson NSObject class
......
通过hopper分析CoreFoundation
-
在lldb调试环境下输入
image list
,搜索CoreFoundation
找到CoreFoundation
的路径,将之放到hopper中分析 -
搜索
image.png_forwarding_prep_0
选中伪代码,可以看到实现如下
-
双击进到
image.png___forwarding___
方法
-
找到了
image.pngforwardingTargetForSelector:
然后跳转到loc_6459b
-
做了僵尸对象处理如果
image.pngmethodSignatureForSelector:
没有实现跳转到loc_64970
-
执行
doesNotRecognizeSelector:
报方法未找到错误
消息转发的继续处理
- forwardingTargetForSelector消息快速转发,直接返回一个能够处理消息的对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [XQStudent alloc];
}
- 如果消息快速转发没有处理就会进入到消息慢速转发
methodSignatureForSelector
,该方法返回一个方法签名,通过官方文档找到和这个方法有关的两个方法forwardInvocation:
和instanceMethodSignatureForSelector:
,forwardInvocation:
是对invocation的进一步处理,通过上面分析指导,在这之后会再进行一次动态方法解析
void doSomething() {
NSLog(@"%s",__func__);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
// [NSMethodSignature methodSignatureForSelector:@selector(doSomething)];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.selector = @selector(doSomething);
doSomething();
// [anInvocation invoke];
}
-
由此可以得到一张关于消息转发处理的流程图
消息转发.png
总结
- 整个消息的调用流程
1,cache的快速查找
2,如果没找到,则在类的方法列表中查找,如果还是没找到,则去父类链的缓存和方法列表中查找
3,如果还没找到,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod 方法
4,如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发
5,如果转发之后也没有,则程序直接报错unrecognized selector sent to instance
网友评论