利用消息轉(zhuǎn)發(fā)機制屏蔽unrecognized selector sent to instance

受到JSPatch的啟發(fā), 想到利用消息轉(zhuǎn)發(fā)機制屏蔽unrecognized selector sent to instance這種崩潰, 這種崩潰的意思是, 給沒有實現(xiàn)的方法發(fā)消息

產(chǎn)生原因

不管是什么原因造成的, 可能是通過運行時performSelector調(diào)用了不存在的方法而并沒有判斷, 也可能是訪問了不存在的屬性或方法編譯器沒有報錯, 導(dǎo)致運行崩潰, 也可能使用了強轉(zhuǎn)等手段但是消息響應(yīng)者并不是我們希望的類型, 都可能會導(dǎo)致程序崩潰. 那先不說如何解決崩潰, 先看看如何讓程序不崩潰(有時候不讓程序崩潰可能也不好, 問題被隱藏起來了, 更難發(fā)現(xiàn)了)

解決原理

原理1

程序在崩潰前系統(tǒng)會給你3次機會進(jìn)行補救, 可以通過重寫

+ (BOOL)resolveInstanceMethod:(SEL)aSEL

也可以通過重寫

- (id)forwardingTargetForSelector:(SEL)aSelector

還可以重寫

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

- (void)forwardInvocation:(NSInvocation *)invocation

具體的道理文獻(xiàn)1文獻(xiàn)2已經(jīng)有比較詳細(xì)的解釋了, 這里不再贅述, 重點說下為什么不用前兩種方式, 因為信息量不夠, 前兩種方式只有一個SEL類型的參數(shù), 也就是只能知道調(diào)用了哪個方法而不知道是誰調(diào)用的, 也就是不知道target是誰, 那么在這里去addMethod是沒法做到通用的, 因為你連target都不知道.怎么能準(zhǔn)確給出程序想要的去處呢! 而且, 實踐時候發(fā)現(xiàn)forwardingTargetForSelector會攔截很多系統(tǒng)的方法, 可能是在這里進(jìn)行重定向, 所以也不適合攔截這個方法.

原理2

利用黑魔法, 通過交換2個方法來達(dá)到攔截系統(tǒng)的forwardInvocationmethodSignatureForSelector的目的, 這里要說明的是, 不一定要在+ load里去交換, 當(dāng)然要做的徹底一些還是應(yīng)該在+ load去做, 因為+ load的執(zhí)行要在main之前, 也就是程序啟動前, 這里僅僅說明原理而且這里用類方法也有其他的好處, 所以本文選擇程序啟動后去Swizzling Method

+ (void)hookNotRecognizeSelector
{
    [MethodsHooker hookMethedClass:[NSObject class] hookSEL:@selector(methodSignatureForSelector:) originalSEL:@selector(methodSignatureForSelectorOriginal:) myselfSEL:@selector(methodSignatureForSelectorMySelf:)];
    [MethodsHooker hookMethedClass:[NSObject class] hookSEL:@selector(forwardInvocation:) originalSEL:@selector(forwardInvocationOriginal:) myselfSEL:@selector(forwardInvocationMySelf:)];
}

+ (void)hookMethedClass:(Class)class hookSEL:(SEL)hookSEL originalSEL:(SEL)originalSEL myselfSEL:(SEL)mySelfSEL
{
    Method hookMethod = class_getInstanceMethod(class, hookSEL);
    Method mySelfMethod = class_getInstanceMethod(self, mySelfSEL);
    
    IMP hookMethodIMP = method_getImplementation(hookMethod);
    class_addMethod(class, originalSEL, hookMethodIMP, method_getTypeEncoding(hookMethod));
    
    IMP hookMethodMySelfIMP = method_getImplementation(mySelfMethod);
    class_replaceMethod(class, hookSEL, hookMethodMySelfIMP, method_getTypeEncoding(hookMethod));
}

這里給一個類方法出來, 里面和可以hook很多崩潰, 我們的unrecognized selector也是其中一種, 可以在AppDelegatedidFinishLaunch里去調(diào)用, 好了看最關(guān)鍵的代碼吧

- (NSMethodSignature *)methodSignatureForSelectorMySelf:(SEL)aSelector {
    NSString *sel = NSStringFromSelector(aSelector);
    if ([sel rangeOfString:@"set"].location == 0) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    } else {
        return [NSMethodSignature signatureWithObjCTypes:"@@:"];
    }
}

- (NSMethodSignature *)methodSignatureForSelectorOriginal:(SEL)aSelector {
    return nil;
}

- (void)forwardInvocationMySelf:(NSInvocation *)anInvocation {
    Class cls = [anInvocation.target class];
    id forwardInvocation = [[cls alloc] init];
    class_addMethod(cls, anInvocation.selector, (IMP)crashFunction, "v@:");
    if ([forwardInvocation respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:forwardInvocation];
    }
}

- (void)forwardInvocationOriginal:(NSInvocation *)anInvocation {
    
}

void crashFunction(id self, SEL _cmd) {
    MFLogError(@"MethodsHook", @"程序崩潰");
    
}

methodSignatureForSelectorMySelfforwardInvocationMySelf是我們用來替換系統(tǒng)methodSignatureForSelectorforwardInvocationMySelf的實現(xiàn), 這里可以隨便返回一個簽名可以和aSelector沒任何關(guān)系, 這里的目的就是要讓系統(tǒng)調(diào)用forwardInvocationMySelf, 在這里anInvocation可以拿到造成崩潰的類名和Selector, 所以這里動態(tài)的去給這個類添加一個實現(xiàn)crashFunction, 這里crashFunction只是簡單的打印了一下日志, 然而, 程序的命運卻完全不一樣了, 這里程序不會崩潰了, 可以繼續(xù)運行了, 這里只是給出一個簡單的demo, 有興趣的讀者可以根據(jù)返回值類型,去造不同的crashFunction, 目的就是讓程序繼續(xù)運行.

<NSInvocation: 0x7fb34a407830>
return value: {@} 0x0
target: {@} 0x7fb34a4134b0
selector: {:} pushTitle

展望

這里分析出崩潰的原因并且屏蔽它并不是我們解決問題的方式, 最終還是要利用JSPatch去修復(fù), 但對用戶的體驗可能不一樣了, 尤其對那些不是很好重現(xiàn)的崩潰(可能是服務(wù)端包亂序, 服務(wù)端返回數(shù)據(jù)錯誤, 并且客戶端沒有保護(hù))還是有很大幫助的, 用戶不會覺得閃退, 可能只是自己卡了下, 這里crashFunction可以擴展的點很多, 可以是去重新進(jìn)行網(wǎng)絡(luò)請求, 或者干脆直接重啟app, 具體的點大家可以多多發(fā)現(xiàn), 有興趣的可以留言和我進(jìn)行交流.

代碼

https://github.com/bigParis/BPUnrecognizedDemo

參考文獻(xiàn)
[文獻(xiàn)1](http://www.csdn.net/article/2015-07-06/2825133-objective-c-runtime/5)
[文獻(xiàn)2](http://www.cnblogs.com/biosli/p/NSObject_inherit_2.html)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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