IOS系統閃退異常(Crash)捕獲處理
阿新 • • 發佈:2020-12-26
我們的程式經常出現異常造成閃退的現象,對於已經發布的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,保住執行緒,處理後再崩潰.