iOS中使用斷言

使用斷言可以有效地防止程序錯誤。斷言要求程序中特定的語句必須爲真。如果不爲真,說明程序正處於一種無法預測的運行狀態,這時候程序不應該繼續執行下去。下面是NSAssert的一個例子:

1
    NSAssert(x == 4, @"x must be four");

如果測試條件返回NONSAssert就會拋一個異常。異常處理程序捕獲異常之後,會調用abort結束程序。Mac開發中,出現異常時只結束當前循環。而在iOS中,在哪個線程中發生異常,默認行爲都是調用abort結束整個程序。


從技術角度來說,abort向進程發送一個SIGABRT信號。信號處理程序會捕獲這個信號然後做出相應處理。不建議自己捕獲SIGABRT信號,除非是要用在crash報告中。10.4節“捕獲與報告程序崩潰”會詳細介紹如何處理程序崩潰。


可以在編譯設置面板的“Preprocessor Macros”(GCC_PREPROCESSOR_DEFINITIONS)中設置NS_BLOCK_ASSERTIONS以禁用NSAssert。至於最終發佈代碼中是否需要禁用NSAssert,不同的人有不同的答案。這取決於當程序處於非法狀態的時候,你是希望它停止運行還是希望它紊亂運行。建議在發佈版代碼中禁用斷言。在某些情況中程序錯誤可能只會造成非常小的問題,但是斷言會導致程序崩潰。Xcode 4在默認情況下會禁用發佈版代碼中的斷言。

雖然我會在發佈版代碼中刪除斷言,但是我並不會忽視斷言錯誤。實際上,斷言錯誤屬於“永遠不該發生”的錯誤,應該記錄到日誌中。設置NS_BLOCK_ASSERTIONS會從程序中完全刪除斷言,而我建議對斷言做些改動以便在日誌中留下記錄。下面代碼中的RNLogBugNSLog的一個別名)函數可以用來記錄日誌。不建議經常使用#define,不過用在這裏是非常必要的,因爲需要把__FILE____LINE__轉換爲調用者代碼所在的文件和行號。

下面的代碼把NSCAssert包裝爲RNCAssert,還定義了一個輔助函數RNAbstract。在C語言中使用斷言的時候應該使用NSAssert

RNAssert.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    #import <Foundation/Foundation.h>

    #define RNLogBug NSLog //如果用的是Lumberjack日誌框架,要把NSLog換成DDLogError

    //RNAssertRNCAssert會記錄日誌(即使是在發佈版代碼中),
    //除此之外它們與NSAssertNSCAssert完全一樣

    #define RNAssert(condition, desc, ...) 
      if (!(condition)) { 
          RNLogBug((desc), ## __VA_ARGS__); 
          NSAssert((condition), (desc), ## __VA_ARGS__); 
      }

    #define RNCAssert(condition, desc) 
      if (!(condition)) { 
          RNLogBug((desc), ## __VA_ARGS__); 
          NSCAssert((condition), (desc), ## __VA_ARGS__); 
      }

斷言應該位於導致程序崩潰的代碼之前。看下面的例子(假設你使用RNAssert記錄日誌,包括在發佈版代碼中):

1
2
    RNAssert(foo != nil, @"foo must not be nil");
    [array addObject:foo];

如果這裏會導致斷言失敗,那麼即使關閉斷言程序也依然會崩潰。所以要將代碼改爲下面這樣:

1
2
3
4
    RNAssert(foo != nil, @"foo must not be nil");
    if (foo != nil) {
      [array addObject:foo];
    }

這樣就好多了,RNAssert可以記錄日誌。但是這裏有冗餘代碼,如果斷言條件和if條件不匹配,就可能產生bug。建議使用下面的方式:

1
2
3
4
5
6
    if (foo != nil) {
      [array addObject:foo];
    }
    else {
      RNAssert(NO, @"foo must not be nil");
    }

這樣就保證了斷言條件跟if條件總是匹配,這是一種比較好的斷言使用方式。另外,建議在switch語句的default分支中使用斷言:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    switch (foo) {
      case kFooOptionOne:
        ...
        break;
      case kFooOptionTwo:
        ...
        break;
      default:
        RNAssert(NO, @"Unexpected value for foo: %d", foo):
        break;
    }

這樣一來,如果foo的可能值增加了而switch塊沒有進行相應更新,那就會導致斷言失敗。

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章