8.消息轉(zhuǎn)發(fā)機(jī)制

一:消息轉(zhuǎn)發(fā) - 方法的實(shí)現(xiàn)沒有實(shí)現(xiàn)

為什說消息轉(zhuǎn)發(fā)呢? 從上一篇方法查找流程中可以看到,如果找不到方法的實(shí)現(xiàn),則app會(huì)崩潰,為了不讓APP崩潰,所以我們來研究下方法的實(shí)現(xiàn)沒有實(shí)現(xiàn)的情況下:

/*
 ///1.慢速流程
 _class_lookupMethodAndLoadCache3
 lookUpImpOrForward
 */
/*
 ///2.方法有聲明,但沒有實(shí)現(xiàn)
 if (resolver  &&  !triedResolver) {
     runtimeLock.unlock();
     _class_resolveMethod(cls, sel, inst);
     runtimeLock.lock();
     // Don't cache the result; we don't hold the lock so it may have
     // changed already. Re-do the search from scratch instead.
     triedResolver = YES;
     goto retry;
 }
 */
/*
 ///3.動(dòng)態(tài)方法決議 (分對象方法和類方法)
 void _class_resolveMethod(Class cls, SEL sel, id inst)
 {
     if (! cls->isMetaClass()) {
         // try [cls resolveInstanceMethod:sel]
         // 對象方法 決議
         _class_resolveInstanceMethod(cls, sel, inst);
     }
     else {
         // try [nonMetaClass resolveClassMethod:sel]
         // and [cls resolveInstanceMethod:sel]
         // 類方法 決議
         _class_resolveClassMethod(cls, sel, inst);
         //判斷
         if (!lookUpImpOrNil(cls, sel, inst,
                             NO, YES, NO))
         {
             // 對象方法 決議
             _class_resolveInstanceMethod(cls, sel, inst);
         }
     }
 }
 */

可以看到最后都走了_class_resolveInstanceMethod(cls, sel, inst);這個(gè)函數(shù)

///動(dòng)態(tài)方法決議
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//容錯(cuò)處理
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
    {
        // Resolver not implemented.
        return;
    }
    
    // 系統(tǒng)給你一次機(jī)會(huì) - 你要不要針對 sel 來操作一下下
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
 //可以看到在這里走了這個(gè)
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    
    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
//容錯(cuò)處理
    IMP imp = lookUpImpOrNil(cls, sel, inst,
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    
    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p",
                         cls->isMetaClass() ? '+' : '-',
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel),
                         cls->isMetaClass() ? '+' : '-',
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

以上可以看出來在獲取imp之前走了 bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
所以走了SEL_resolveInstanceMethod中的resolveInstanceMethod方法,我們來搜索一下resolveInstanceMethod


截屏2020-03-18下午5.39.48.png

截屏2020-03-18下午5.40.24.png

從這里可已看到NSObject實(shí)現(xiàn)了resolveInstanceMethod這個(gè)方法,所以如果我們重寫這個(gè)方法,做些處理是否可以避免崩潰呢,來看下邊

二:消息轉(zhuǎn)發(fā) - 對象的resolveInstanceMethod重寫

///4.對象方法動(dòng)態(tài)決議(此方法寫在沒有實(shí)現(xiàn)的類的.m文件里)
+ (BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"來了對象方法:%s - %@",__func__,NSStringFromSelector(sel));

    if (sel == @selector(saySomething)) {
        NSLog(@"說話了");
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }

    return [super resolveInstanceMethod:sel];
}

運(yùn)行下app,可以看到


截屏2020-03-18下午5.44.21.png

從上可以看到這樣是可行的,那么類方法的呢?

三:消息轉(zhuǎn)發(fā) - 類的class_resolveClassMethod重寫

else {
         // try [nonMetaClass resolveClassMethod:sel]
         // and [cls resolveInstanceMethod:sel]
         // 類方法 決議
         _class_resolveClassMethod(cls, sel, inst);
         //判斷
         if (!lookUpImpOrNil(cls, sel, inst,
                             NO, YES, NO))
         {
             // 對象方法 決議
             _class_resolveInstanceMethod(cls, sel, inst);
         }
     }

從上邊可以看到先走的 _class_resolveClassMethod(cls, sel, inst);所以搜索下:

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

可以看到這個(gè)方法跟對象的resolveInstanceMethod非常想

//重點(diǎn)
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

所以搜索下resolveClassMethod


截屏2020-03-18下午5.51.39.png

截屏2020-03-18下午5.51.49.png

這個(gè)又是系統(tǒng)給實(shí)現(xiàn)了,所以我們可以在里面做一番操作

///5.類方法動(dòng)態(tài)決議
+ (BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@"來了類方法:%s - %@",__func__,NSStringFromSelector(sel));

     if (sel == @selector(sayLove)) {
         NSLog(@"說- 說你你愛我");
         IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
         Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
         const char *sayHType = method_getTypeEncoding(sayHMethod);
         // 類方法在元類 objc_getMetaClass("LGStudent")
         return class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
     }

     return [super resolveClassMethod:sel];
}

運(yùn)行下app


截屏2020-03-18下午5.53.41.png

可以看到?jīng)]有崩潰
總結(jié):

/*
 類方法 如果找不到 - 動(dòng)態(tài)方法決議
 -> 類 -> 元類 -> NSObject
 1: resolveClassMethod 你是否處理 -> 只要注意元類
 2: resolveClassMethod 沒有處理 -> resolveInstanceMethod
*/

四:方法崩潰下走了哪些

2020-03-18 17:57:19.053411+0800 LGTest[20115:862215] -[LGStudent saySomething]: unrecognized selector sent to instance 0x1006a82e0
2020-03-18 17:57:19.054268+0800 LGTest[20115:862215] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[LGStudent saySomething]: unrecognized selector sent to instance 0x1006a82e0'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff3c9ca8ab __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x00007fff72c84805 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff3ca49b61 -[NSObject(NSObject) __retain_OA] + 0
    3   CoreFoundation                      0x00007fff3c92eadf ___forwarding___ + 1427
    4   CoreFoundation                      0x00007fff3c92e4b8 _CF_forwarding_prep_0 + 120
    5   LGTest                              0x0000000100000dc6 main + 70
    6   libdyld.dylib                       0x00007fff73ff27fd start + 1
    7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

或崩潰的堆棧

截屏2020-03-18下午5.57.55.png

從上邊可以看到崩潰之前有走forwarding, _CF_forwarding_prep_0
然后我們想一下怎么更清晰的分析下

1. _class_lookupMethodAndLoadCache3
2. lookUpImpOrForward
3.log_and_fill_cache

都走這個(gè)方法

//log和填充緩存
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    //打印消息
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}


bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;

///消息發(fā)送
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 如果是TRUE則開啟日志
            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;
}

///flag
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 如果是TRUE則開啟日志,所以外部調(diào)用修改一下,就會(huì)將生成的日志存入/tmp/msgSends中

///1.函數(shù)方法的擴(kuò)充
extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGStudent *student = [LGStudent alloc] ;

        // 動(dòng)態(tài)方法決議
        // 對象方法
        // 類方法
        
        instrumentObjcMessageSends(true);//開啟日志
        [student saySomething];
        instrumentObjcMessageSends(false);//關(guān)閉日志

    }
    return 0;
}

運(yùn)行下程序生成的日志在

在主屏幕空白處 command + shift + g 輸入以下地址
地址為: /private/tmp
截屏2020-03-18下午3.40.15.png

打開里面可以看到


截屏2020-03-19上午9.00.02.png

有此可知系統(tǒng)崩潰前走了這些方法

///對象
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:

- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:

- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:

///類
- LGStudent NSObject class 
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:

///系統(tǒng)的報(bào)錯(cuò)方法(傳0不報(bào)錯(cuò),有方法報(bào)錯(cuò))
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:

///以下為崩潰的系統(tǒng)信息了
- LGStudent NSObject class
- OS_xpc_serializer OS_xpc_object dealloc
- OS_object NSObject dealloc
+ OS_xpc_payload NSObject class
- OS_xpc_payload OS_xpc_payload dealloc
- NSObject NSObject dealloc

所以我們可以在奔潰前在這些方法里做處理,已防止奔潰,以下崩潰前的消息轉(zhuǎn)發(fā)(特殊處理)

//第一種,動(dòng)態(tài)解析,在里邊處理(上一章7講過)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    return [super resolveInstanceMethod:sel];
}
//第二種
// 別人可能有?(1不處理走2)
// 快速流程 - 交給一個(gè)對象來處理
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
//第三種
// 慢速轉(zhuǎn)發(fā) --> 漂流瓶 (這兩步是一起使用的)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//漂流瓶
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"漂流瓶: %s",__func__);

     //事情 - 事務(wù) - 秘書 - 失效
     //系統(tǒng)本質(zhì)
   SEL aSelector = [anInvocation selector];

  //判斷
    if ([[LGTeacher alloc] respondsToSelector:aSelector]) {
   //交給別人處理
        [anInvocation invokeWithTarget:[LGTeacher alloc]];
    }
    else {
        //系統(tǒng)
       [super forwardInvocation:anInvocation];
    }
}

總結(jié):

/*
 imp = 方法名 + 簽名
 */

/*
 1.方法查找流程:
 
 1: objc_msgSend -> 查找imp -> 緩存
 2: 慢速遞歸 -> 沒有 -> 即將奔潰 -> 開始特殊處理
 */

/*
 2.崩潰前的特殊處理->消息轉(zhuǎn)發(fā):
 
 1: 你是不是有特殊處理
 2: 有沒有你交給別人處理
 3: 意味著你不想處理 : forwardingTargetForSelector
    1: methodSignatureForSelector
    2: forwardInvocation : anInvocation
    3: 在里面處理就OK
    4: 簡單的不喜歡這個(gè)方法而已 : 系統(tǒng)就不會(huì)分發(fā)這個(gè)事務(wù)
 4: doesNotRecognizeSelector
 */

五:最后介紹一種iOS逆向生成iOS偽代碼的方式

先找到CoreFoundation


截屏2020-03-18下午4.42.25.png

截屏2020-03-18下午4.40.49.png

然后找一個(gè)逆向編碼工具,將CoreFoundation拖進(jìn)去
推薦工具:
hopper
截屏2020-03-19上午9.16.25.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容