對這兩年修復的bug作了個簡單的總結。html
SIGSEGVios
通常狀況下,SIGSEGV是因爲內存地址不合法形成。由於無效的內存訪問致使的,通常是指針指向不存在的地址所致使(Invalid memory reference);數組
SIGBUS網絡
通常狀況下,SIGBUS是由於內存地址沒有對齊致使。數據結構
由於總線出錯(bus error)。地址通常是先校驗地址對齊再校驗其餘的,校驗地址對齊後會放入數據總線,這時有問題就會報SIGBUS的錯誤。測試
SIGABRTspa
異常終止條件,例如abort()。指針
一、三、6是修復crash過程當中常遇到的crash類型。code
1. crashName=NSInvalidArgumentException ,crashReason=data parameter is nilorm
(1) [aMutableDictionary setObject:nil forKey:]; object can not be nil. [__NSDictionaryM removeObjectForKey:nil]: key cannot be nil (2) [aString hasSuffix:nil]; nil argument crash. [aString hasPrefix:nil]; nil argument crash. (3) aString = [NSMutableString stringWithString:nil];nil argument crash. aString = [[NSString alloc] initWithString:nil]; nil argument crash. (4) aURL = [NSURL fileURLWithPath:nil]; nil argument crash. (5) [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];data is nil crash (6) [aMutableArray addObject:nil], 添加nil crash。 (7) UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = nil;// setString參數nil則crash (8)if(controller.presentedViewController != nil) { [controller dismissViewControllerAnimated:NO completion:^{ //controller.presentedViewController爲nil則presentViewController:方法會crash [controller presentViewController:self.imagePickerController animated:YES completion:nil]; }]; }else{ [controller presentViewController:self.imagePickerController animated:YES completion:nil]; }
2. 野指針
(1) self.dataArr = (NSMutableArray *)mopayConsumeOrderList.list;
[self.dataArr removeAllObjects];
其中mopayConsumeOrderList.list是NSArray * 類型,
運行後出現程序崩潰,緣由是self.dataArr和mopayConsumeOrderList.list指向同一段內存,self.dataArr在運行屢次以後mopayConsumeOrderList.list指向空時self.dataArr也變成野指針。
改爲:self.dataArr 本身new一個,即:
self.dataArr = [NSMutableArray arrayWithArray:self.mopayConsumeOrderList.list];
(2)屬性的內存管理語義設置的_unsafe_unretain,出現野指針;
(3) ios9.0以前Notification未註銷帶來的殭屍對象問題。
3. 數據類型錯誤帶來的unrecognized selector crash
例1:常見業務調用時傳參數應爲NSString,業務方傳入的數據爲Dictionary,崩潰.
例2: 網絡層中將NSData轉換成Model,返回的數據和移動之家上定義的數據不一致,帶來的崩潰。
例3: push 接收到的消息是string類型,當作NSNumber類型處理。
4. 數組取值越界crash
5. 關注新技術
(1)2017年6月開始蘋果熱修復crash
(2)ios10之後push使用UNUserNotificationCenter註冊push,不然ios十、ios11都有可能crash。
(3)ios10等若是沒在plist裏設置key-value描述語,不然相機崩潰(藍牙、日曆、麥克風、相機、相冊、通信錄等)
<key>NSPhotoLibraryUsageDescription</key>
<string>APP須要您的容許,才能訪問相冊</string>
6. 數據競爭帶來的crash (SIGSEGV)
例1:NSMutableDictionary類型用objectForKey取數據和setValue: forKey:設置數據,有可能存在數據競爭,須要作保護。
例2: NSMutableArray存在一樣的問題。遍歷數組期間,不要對數組進行remove和insert操做,由於數組有保護機制,容易崩潰。能夠先拷貝一份出來,對拷貝的數組遍歷,對真正須要改變的數組操做。
7. selector不存在帶來的crash (SIGSEGV)
[self respondsToSelector:@sel(aSel)]的判斷,不然有可能帶來的unrecognized selector crash。
例1. [toggle addTarget:target action:selector forControlEvents:UIControlEventValueChanged]; // selector是否存在
例2. [self performSelector:@selector(aSelector)]; // aSelector是否存在
8. 存儲類型必須是對象類型
[self.dic setObject:[NSArray arrayWithArray: self.mpShopArr] forKey:@"shopArr"];
[[NSUserDefaults standardUserDefaults] setObject:self.dic forKey:[JVAccountManager sharedAccount].edper];崩潰
因爲self.mpShopArr裏存的數據結構包含了不少類型,其中一個數據類型是int,不是object,致使第一句沒報錯、但第二句就報錯了。
最終存到[NSUserDefaults standardUserDefaults] 裏的數據類型,不管包裝多少層,全部的內容必須是對象類型。
9. 內存泄漏(bug)
(1)單例+RACObserve帶來內存泄漏。
[RACObserve(self.viewModel, modulesShouldRefresh) subscribeNext:^(NSNumber * modulesShouldRefresh) {
@strongify(self);
if ([modulesShouldRefresh boolValue] == YES) {
[self setShowLoading:NO];
[self.highFrequencyTitleView requestHeadInfoForce:@(YES)];
[self refreshModules:YES];
} }];
因爲viewModel設成了不恰當的單例,對其RACObserve致使self(homeVC)不能釋放,也就是當切換帳號的時候,原來的homeVC沒釋放,新的homeVC又建立了一個,致使下拉刷新時,原來的沒有釋放的homeVC觸發了接口,如今的homeVC也觸發了接口,致使接口請求屢次。因此,單例不能隨便亂用,除非能夠肯定對象與APP同生共死生命週期沒有問題。
(2)performSelector: withObject: afterDelay: 必須有合適的時機cancelPreviousPerformRequestsWithTarget:selector:object:,不然會致使內存泄漏。
能夠用weak類型的dispatch_after避免內存泄漏:
__weak CRMHomeViewModel *weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf requestLiveData:YES];
});
(3) NSTimer會保留其目標對象,若是不加以注意,就會持有保留環,形成內存泄露。
scheduledTimerWithTimeInterval: target:self selector: userInfo: repeats:,重複模式的計時器,必須手動調用[_aTimer invalidate]方法,才能令其中止。
self持有timer,timer的target又是self, timer不釋放,致使self也不能調用dealloc,因此timer調用invalidate的時機也很差把控。ios10之後,使用block來打破保留環,定義一個弱引用,令其指向self,而後使塊捕獲這個引用,而不直接去捕獲普通的self變量。也就是說,self不會爲計時器所保留。當塊開始執行時,馬上生成strong引用,以保證明例在執行期間持續存活:
__weak SLVTimerTestViewController *weakSelf = self; _aTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 repeats:YES block:^(NSTimer* timer){ SLVTimerTestViewController *strongSelf = weakSelf; [strongSelf timer_invoke]; }];
參考《Baymax:網易iOS App運行時Crash自動防禦實踐》,並進行了一些調研和實踐,對如下5種常見crash進行了分析,並寫出了對應的防禦代碼。寫好的代碼已通過初步測試。前進的一小步,記錄下來。對應的4篇博客: