基本消息機制流程:iOS 底層 - runtime之objc_msgSend
什么情況下會走兩次動態(tài)方法決議呢 子姜?
以對象方法為例在來到消息動態(tài)轉(zhuǎn)發(fā)階段后會執(zhí)行以下邏輯
未動態(tài)解析過:調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel
并添加了方法的實現(xiàn),解析成功 底層執(zhí)行 goto retry冰评,標記為已經(jīng)動態(tài)解析 走消息發(fā)送流程:從receiverClass的cache中查找方法(lookUpImpOrForward)這一步開始執(zhí)行已經(jīng)動態(tài)解析過:直接走消息轉(zhuǎn)發(fā)-->
_objc_msgForward_impcache
,
調(diào)用- (id)forwardingTargetForSelector:(SEL)aSelector
看是否轉(zhuǎn)發(fā)給其他實例來實現(xiàn), 返回空就調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
看是否返回了方法簽名, 返回方法簽名就再走一次動態(tài)方法決議, 結(jié)果還是沒動態(tài)添加方法就會來到forwardInvocation
到此執(zhí)行結(jié)束
結(jié)論: 當(dāng)我們沒有動態(tài)添加方法實現(xiàn)卻返回了方法簽名時會出發(fā)二次動態(tài)決議,個人猜測蘋果認為你返回了方法簽名 那你大概率也會有對應(yīng)的實現(xiàn) 所以forwardInvocation
之前就再次執(zhí)行了動態(tài)決議, 避免在動態(tài)決議之后 && 方法簽名返回之前你悄悄的給動態(tài)添加了方法的實現(xiàn)
比如你直接就在methodSignatureForSelector中動態(tài)添加了方法的實現(xiàn)
代碼測試
@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__);
}
打印結(jié)果
來到--> +[LPersion resolveInstanceMethod:]
來到--> -[LPersion forwardingTargetForSelector:]
來到--> -[LPersion methodSignatureForSelector:]
來到--> +[LPersion resolveInstanceMethod:]
來到--> -[LPersion forwardInvocation:]
由于_objc_msgForward_impcache內(nèi)部的實現(xiàn)沒有開源,所以下面用下面方式來探索后續(xù)流程
lookUpImpOrForward--> log_and_fill_cache--> logMessageSend--> instrumentObjcMessageSends
開啟 instrumentObjcMessageSends
需要用到extern
關(guān)鍵字率拒,這個關(guān)鍵字是可以定義和聲明一個外部函數(shù)(沒有被static修飾的)
這個日志開啟之后溶握,會在 /private/tmp目錄下創(chuàng)建一個 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);
}