若是在動態方法決議的流程仍是沒有找到方法呢?最後會返回nil
或者_objc_msgForward_impcache
!markdown
那麼是否是就沒挽救的餘地了呢?app
咱們能夠經過instrumentObjcMessageSends
來打印objc
在底層的相關日誌
:函數
@interface HPerson : NSObject
- (void)sayNO;
@end
@implementation HPerson
@end
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPerson * p = [HPerson alloc];
instrumentObjcMessageSends(YES);
[p sayNO];
instrumentObjcMessageSends(NO);
}
return 0;
}
複製代碼
在objc
源碼搜索instrumentObjcMessageSends
:oop
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;
}
複製代碼
這裏主要是給objcMsgLogEnabled
賦值,而objcMsgLogEnabled
則影響日誌打印:網站
/*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementation in question. **********************************************************************/
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
cls->cache.insert(sel, imp, receiver);
}
複製代碼
再進入到logMessageSend
:ui
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;
}
複製代碼
發現日誌寫入到了/tmp
文件夾,運行後就能夠看到日誌文件:this
咱們發現resolveInstanceMethod
是動態方法決議的過程,可是以後的forwardingTargetForSelector
又是什麼呢?url
咱們能夠先command + shift + 0
,來打開開發文檔進行查閱:spa
能夠得知這個方法是一個重定向的過程!3d
在類中先重寫forwardingTargetForSelector
方法,由於咱們調用的是對象方法,因此這裏就是重寫-方法
並運行:
發現確實進入到了forwardingTargetForSelector
方法!
那麼咱們就能夠把這個方法轉交給其餘類進行執行!
建立一個HClass
類,實現sayNO
方法:
這樣就完成了消息轉發,而且不像動態方法決議那樣臃腫!
以上就是快速轉發流程了!
若是HClass
類並無實現sayNO
方法呢?
那麼就會進入到methodSignatureForSelector
方法,即慢速轉發流程!
依舊先打開開發文檔進行查閱:
可得知這是一個返回方法簽名的過程!
在類中先重寫methodSignatureForSelector
方法,由於咱們調用的是對象方法,因此這裏就是重寫-方法
並運行:
發現確實進入到了methodSignatureForSelector
方法!
感受開發文檔可知這個方法須要搭配NSInvocation
,以及返回適當的方法簽名,即NSMethodSignature
:
成功執行,可是卻沒有任何的實現!
由於在iOS中有事務這個概念,便可執行也可不執行,所以方法保存到了簽名裏面,在有須要的時候便可提取:
或者按照開發文檔的案例進行處理:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
複製代碼
雖然消息轉發流程以及有所瞭解了,可是咱們並無在源碼中看到調用的過程,那麼究竟是怎麼被調用的呢?
先用lldb
中用bt
查看堆棧:
能夠看到在方法報錯前執行了3
個方法:
__forwarding_prep_0___
--> ___forwarding___
--> doesNotRecognizeSelector
並且這3個方法都屬於CoreFoundation
動態庫!
咱們能夠在蘋果開源網站下載,可是在CoreFoundation
動態庫內並無找到相對應的方法,說明蘋果並無徹底的開源!
那麼咱們只能進行逆向了!
要逆向首先要有可執行文件!
用模擬器在lldb
中用image list
進行查看:
而後把CoreFoundation拖入Hopper中,打開僞代碼模式,搜索__forwarding_prep_0___
函數:
和咱們在bt
中看到流程同樣,接着進入到了 ___forwarding___
函數!
進入 ___forwarding___
函數:
先判斷是否有forwardingTargetForSelector
方法。
若是forwardingTargetForSelector
方法存在,即進入快速轉發流程,調用forwardingTargetForSelector
方法。
接着判斷返回值:
若是返回值爲空或者和當前對象同樣,則與沒有forwardingTargetForSelector
方法同樣,進入到loc_115baf
!
若是有返回值或者和當前對象不同,則通過處理後直接返回結果!
若是沒有forwardingTargetForSelector
方法!
則進入到loc_115baf
:
1:判斷是不是殭屍對象,不是則繼續,是則跳轉到
loc_115f34
,即14
處。2:判斷是否有
methodSignatureForSelector:
方法,有則繼續,沒有則跳轉到loc_115f4a
,即13
處。3:執行
methodSignatureForSelector:
方法,即慢速轉發流程
,並判斷返回值,有值則繼續,值爲空則跳轉到loc_115fc5
,即10
處。4:判斷是否有
_forwardStackInvocation:
方法,有則繼續,沒有則跳轉到loc_115d65
,即7
處。5:執行
_forwardStackInvocation:
方法。6:跳轉到
loc_115ef5
。7:沒有
_forwardStackInvocation:
方法,即跳轉到此,判斷是否有forwardInvocation:
方法,有則繼續,沒有則跳轉到loc_115f92
,即9
處。8:執行
forwardInvocation:
方法,並跳轉到loc_115dd2
,和執行_forwardStackInvocation:
方法同樣。9:沒有
forwardInvocation:
方法,打印錯誤並繼續。10 - 12:判斷是否有
doesNotRecognizeSelector:
方法,並執行,這就是最後的找不到方法的報錯
!13:打印錯誤並跳轉到
loc_115fbe
,即10
處。14:打印錯誤並跳轉到
loc_115f4a
,即13
處
能夠看到不管是_forwardStackInvocation:
方法仍是forwardInvocation:
方法,最後都會到loc_115ef5
:
loc_115ef5:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
複製代碼
即直接返回處理過的結果。
而全部的沒有找到相對應的方法最終都會執行doesNotRecognizeSelector
方法:
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
複製代碼
即方法沒有找到的報錯!
在反彙編流程中能夠看到系統調用了一個_forwardStackInvocation
方法,這個方法並無對外暴露,可是咱們也能夠經過重寫這個方法來看看效果:
確實和咱們看到的流程同樣,有_forwardStackInvocation
方法的時候就不會在走forwardInvocation:
方法了!
以前在動態方法決議的時候,發現動態方法決議會被調用2次,這是爲何呢?
在objc源碼的對象動態方法決議裏面打上斷點,bt查看堆棧:
發現第二進來是由於在CoreFoundation
庫中的methodSignatureForSelector
方法裏的__methodDescriptionForSelector
方法調用了objc庫中的class_getInstanceMethod
方法!
咱們先看看objc源碼中的methodSignatureForSelector
方法:
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
複製代碼
能夠發現是沒有CoreFoundation
庫不可用!
說明真正的源碼是在CoreFoundation
庫中,去反彙編
中進行查看:
發現確實有__methodDescriptionForSelector
方法,進一步跟進:
發現確實調用了class_getInstanceMethod
方法,再去objc源碼中進行查看:
/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel) {
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
複製代碼
發現的確調用了lookUpImpOrForward
函數!
因此動態方法決議
會被調用2
次!