基本消息机制流程:iOS 底层 - runtime之objc_msgSend
什么情况下会走两次动态方法决议呢 ?
以对象方法为例在来到消息动态转发阶段后会执行以下逻辑
-
未动态解析过:调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
并添加了方法的实现,解析成功 底层执行 goto retry,标记为已经动态解析 走消息发送流程:从receiverClass的cache中查找方法(lookUpImpOrForward)这一步开始执行 -
已经动态解析过:直接走消息转发-->
_objc_msgForward_impcache
,
调用- (id)forwardingTargetForSelector:(SEL)aSelector
看是否转发给其他实例来实现, 返回空就调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
看是否返回了方法签名, 返回方法签名就再走一次动态方法决议, 结果还是没动态添加方法就会来到forwardInvocation
到此执行结束
结论: 当我们没有动态添加方法实现却返回了方法签名时会出发二次动态决议,个人猜测苹果认为你返回了方法签名 那你大概率也会有对应的实现 所以forwardInvocation
之前就再次执行了动态决议, 避免在动态决议之后 && 方法签名返回之前你悄悄的给动态添加了方法的实现
比如你直接就在methodSignatureForSelector中动态添加了方法的实现
代码测试
@interface LPersion : NSObject
- (void)test;
@end
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"来到--> %s",__func__);
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"来到--> %s",__func__);
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"来到--> %s",__func__);
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"来到--> %s",__func__);
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"来到--> %s",__func__);
}
打印结果
来到--> +[LPersion resolveInstanceMethod:]
来到--> -[LPersion forwardingTargetForSelector:]
来到--> -[LPersion methodSignatureForSelector:]
来到--> +[LPersion resolveInstanceMethod:]
来到--> -[LPersion forwardInvocation:]
由于_objc_msgForward_impcache内部的实现没有开源,所以下面用下面方式来探索后续流程
lookUpImpOrForward--> log_and_fill_cache--> logMessageSend--> instrumentObjcMessageSends
开启 instrumentObjcMessageSends
需要用到extern
关键字,这个关键字是可以定义和声明一个外部函数(没有被static修饰的)
这个日志开启之后,会在 /private/tmp目录下创建一个 msgSends-xxxx文件
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;
}
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;
}
示例
extern instrumentObjcMessageSends(BOOL flag);
@interface ViewController ()
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
instrumentObjcMessageSends(true);
[[LPersion new] test];
instrumentObjcMessageSends(false);
}
网友评论