我們的程序經常出現異常造成閃退的現象,對于已經發布的APP,如何捕捉到這些異常,及時進行更新解決閃退,提高體驗感呢?
對于一些簡單,比如一些后臺數據的處理,容易重現數組越界,字典空指針錯誤的,我們用oc的runtime方法進行捕獲。比如NSArray的數組越界問題。
源碼地址:GitHub地址
//
// ViewController.m
// CatchCrash
//
// Created by Sem on 2020/8/28.
// Copyright ? 2020 SEM. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSArray *dd =@[@"1",@"2"];
NSString *z =dd[3];
NSLog(@"~~~~~%@",z);
}
@end
我們可以通過runtime進行方法替換,比如我們捕獲NSArray的數組越界問題,注意NSArray 是個類簇所以不能簡單添加類目
+(BOOL)SQ_HookOriInstanceMethod:(SEL)oriSel NewInstanceMethod:(SEL)newSel{
Class class = objc_getRequiredClass("__NSArrayI");
Method origMethod = class_getInstanceMethod(class, oriSel);
Method newMethod = class_getInstanceMethod(self, newSel);
if(!origMethod||!newMethod){
return NO;
}
method_exchangeImplementations(origMethod, newMethod);
return YES;
}
-(id)objectAtIndexedSubscriptNew:(NSUInteger)index{
if(index>=self.count){
//代碼處理 上傳服務器.
return nil;
}
return [self objectAtIndexedSubscriptNew:index] ;
}
當然這種捕獲只能捕獲單一的問題,還有其他的報錯,那就要寫很多的分類處理,如何進行統一的捕捉呢,我們查看下報錯信息看下能不找到有用的信息。
image.png
如圖我們看了報錯的方法棧。看到有libobjc的調用。這個就很熟悉了,去看下runtime的源碼。可以找到set_terminate設置中止的回調,也就是如果出現報錯,系統會回調這個函數,如果外界沒有傳這個函數objc_setUncaightExceptionHandler,系統會使用默認的實現。 我們只要調用NSSetUncaughtExceptionHandler就可以設置這個方法句柄,系統出現報錯時候,回調這個方法,從而讓我們對這個錯誤進行處理.
在AppDelegate里面設置這個方法句柄
NSSetUncaughtExceptionHandler(&HandleException);
然后就可以捕捉異常 ,上傳服務或者保存在本地。
void HandleException(NSException *exception)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
//獲取方法調用棧
NSArray *callStack = [UncaughtExceptionHandler backtrace];
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo
setObject:callStack
forKey:UncaughtExceptionHandlerAddressesKey];
[[[[UncaughtExceptionHandler alloc] init] autorelease]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException
exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
然后在這個對象中通過runloop,保住線程,處理后再崩潰.