受到
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)的forwardInvocation和methodSignatureForSelector的目的, 這里要說明的是, 不一定要在+ 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也是其中一種, 可以在AppDelegate的didFinishLaunch里去調(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", @"程序崩潰");
}
methodSignatureForSelectorMySelf和forwardInvocationMySelf是我們用來替換系統(tǒng)methodSignatureForSelector和forwardInvocationMySelf的實現(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)