Container crash(數組越界,插nil等)
NSString crash (字符串操作的crash)
NSTimer crash
unrecognized selector crash (已完成,未開啟功能)
KVO crash
NSNotification crash
Bad Access crash (野指針)
常見的Crash
1.可變數組红且,字典,字符串
- index越界
- 插入nil
解決方案:使用分類暇番,增加安全,代替所有拋異常的方法
2. unrecognized selector
- h文件聲明方法但是在.m文件中沒有實現
- 調用代理類的方法的時候沒有判斷代理類是否已經實現對應的方法而直接調用
- 對于id類型的對象沒有判斷類型直接強轉調用方法壁酬,從數組取出數據
- 在低版本的系統用了高版本才有的API,NSString的
containsString
厨喂,iOS 8之后才能用
3. 野指針訪問,EXC_BAD_ACCESS
- 代理蜕煌,誤用assign
- block回調之前并沒有判空而是直接調用
原因:block底層是結構體,會根據偏移量找到函數實現斜纪。64位機器上文兑,如果block為nil,則偏移量為0x10腺劣,訪問該地址就crash了。32位系統為0xc - 使用CoreFoundation橘原,重復釋放
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef cfString = CFUUIDCreateString(NULL, uuid);
NSString *string = (__bridge_transfer NSString *)cfString;
CFRelease(cfString);
- NSNotification/KVO 只addObserver并沒有removeObserver
解決方案:通過代碼Review,形成代碼坑列表
NSNotification
沒有移除observer趾断,當observer銷毀之后,如果再發(fā)送notification芋酌,observer會掛掉
方法1:hook NSObject的dealloc函數
- 在dealloc之前remove一次
- 同時,需要hook了NSNotificationCenter的 addObserver方法脐帝,給observer添加一個標記
缺點:dealloc作為最為基礎,調用次數最為頻繁的方法之一堵腹。如對此方法進行替換,一是代碼的引入對工程影響范圍太大秸滴,二是執(zhí)行的代價較大
方法2:給observer添加一個關聯對象
- observer銷毀時,需要先銷毀關聯對象荡含,重寫關聯對象的dealloc方法咒唆,就可以先把observer從NSNotificationCenter移除
KVO
- 沒有移除observer的crash
- 重復移除的crash
- 觀測者必須實現observer方法
解決方案1 使用中間對象
- hook被觀測者的addObserver和removerObserver方法释液,增加一個中間對象,管理key和所有觀察者的關系
- 重寫observerForKey將方法傳遞到真正的觀察者
- 被觀察者dealloc時误债,將中間對象和所有觀察者置為nil
Crash防護
1.可變數組,字典寝蹈,字符串
只需要hook容易發(fā)生crash的方法李命,需要注意的是
- 類簇箫老,真實的類型是不確定的,比如NSArray背后有6種類型,常見是
__NSArray0
阔籽,__NSArrayI
,__NSArrayM
笆制,如果只有一個元素,則是__NSSingleObjectArrayI
在辆,這是iOS 10之后才有的 - NSMutableArray在ARC下會crash,使用MRC編譯可以解決开缎。懷疑是hook之前,已經有很多地方在使用NSMutableArray奕删,中途hook出的問題
- 不建議hook,因為只要蘋果修改背后的實現完残,就兜不住了。應該使用分類谨设,添加安全方法
swizzleInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(objectAtIndex:), @selector(hookObjectAtIndex:));
swizzleInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(objectAtIndex:), @selector(hookObjectAtIndex:));
2. unrecognized selector
主要是利用發(fā)送消息的過程
- 動態(tài)方法解析,需要添加新的方法扎拣,污染當前類,不適合
- hook
forwardingTargetForSelector
素跺,是合適,但是后來發(fā)現指厌,使用協議時,如果類并沒有實現該協議方法踩验,還是會閃退。因為sign還是返回了值
- (id)forwardingTargetForSelectorSwizzled:(SEL)selector{
NSMethodSignature* sign = [self methodSignatureForSelector:selector];
if (sign == nil) {
id stub = [[UnrecognizedSelectorHandle new] autorelease];
class_addMethod([stub class], selector, (IMP)unrecognizedSelector, "v@:");
return stub;
}
return [self forwardingTargetForSelectorSwizzled:selector];
}
- 最后hook下面的方法
- (NSMethodSignature*)methodSignatureForSelectorSwizzled:(SEL)aSelector {
NSMethodSignature* methodSignature = [self methodSignatureForSelectorSwizzled:aSelector];
if (methodSignature) {
return methodSignature;
}
IMP originIMP = class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:));
IMP currentClassIMP = class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:));
// If current class override methodSignatureForSelector return nil
if (originIMP != currentClassIMP){
return nil;
}
// Customer method signature
// void xxx(id,sel,id)
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocationSwizzled:(NSInvocation*)invocation{
NSString* message = [NSString stringWithFormat:@"Unrecognized selector class:%@ and selector:%@",NSStringFromClass(self.class),NSStringFromSelector(invocation.selector)];
handleCrashException(JJExceptionGuardUnrecognizedSelector,message);
}
3. 野指針訪問牡借,EXC_BAD_ACCESS
可以使用黑名單,不進行hook哪些類钠龙。
- Hook住dealloc方法
- 如果當前實例在黑名單里,就把當年前實例加入集合,使用objc_destructInstance方法刻像,清理該實例的引用對象、關聯對象并闲、weak指針。但該實例并未真正釋放內存帝火,并將object_setClass設置成自己的中間對象。一定要保存當前實例犀填。后面需要根據規(guī)則,釋放該實例
- Hook中間對象的方法九巡,收到的消息都由中間對象來處理
- 維護的野指針集合图贸,要么根據個數來維護冕广,要么根據總大小來維護,當滿了撒汉,就需要真正釋放對象內存free(obj)
- 帶來的問題:當前
4.NSTimer
使用臨時對象,持有真正的target和timer
- (void)fireTimer{
if (!self.target) {
[self.timer invalidate];
self.timer = nil;
handleCrashException(JJExceptionGuardNSTimer,[NSString stringWithFormat:@"Need invalidate timer from target:%@ method:%@",self.targetClassName,self.targetMethodName]);
return;
}
if ([self.target respondsToSelector:self.selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:self.timer];
#pragma clang diagnostic pop
}
}
參考資料
http://www.reibang.com/p/04774b4adb58
Block崩潰的原因:https://blog.csdn.net/u011619283/article/details/53139201