iOS常見bug

前言:

對這兩年修復的bug作了個簡單的總結。html

1、常見crash名詞解釋

SIGSEGVios

通常狀況下,SIGSEGV是因爲內存地址不合法形成。由於無效的內存訪問致使的,通常是指針指向不存在的地址所致使(Invalid memory reference);數組

SIGBUS網絡

通常狀況下,SIGBUS是由於內存地址沒有對齊致使。數據結構

由於總線出錯(bus error)。地址通常是先校驗地址對齊再校驗其餘的,校驗地址對齊後會放入數據總線,這時有問題就會報SIGBUS的錯誤。測試

SIGABRTspa

異常終止條件,例如abort()。指針

2、常見crash/bug分類整理

一、三、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則crash8if(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取數據和setValueforKey:設置數據,有可能存在數據競爭,須要作保護。

  例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]; 
}];

3、crash防禦

參考《Baymax:網易iOS App運行時Crash自動防禦實踐》,並進行了一些調研和實踐,對如下5種常見crash進行了分析,並寫出了對應的防禦代碼。寫好的代碼已通過初步測試。前進的一小步,記錄下來。對應的4篇博客:

  1.  [crash詳解與防禦] unrecognized selector crash
  2.  [crash詳解與防禦] Container類型的crash和NSString類型的crash

  3.  [crash詳解與防禦] NSTimer crash

  4.  [crash詳解與防禦] NSNotification crash
相關文章
相關標籤/搜索