斷言Assertion

什麼是斷言

在程序設計中,斷言(assertion)是一種放在程序中的一階邏輯(如一個結果爲真或是假的邏輯判斷式),目的是爲了標示與驗證程序開發者預期的結果

-當程序運行到斷言的位置時,對應的斷言應該爲真。若斷言不爲真時,程序會中止運行,並給出錯誤消息。

使用斷言

  • 用錯誤處理代碼來處理預期會發生的狀況,用斷言來處理絕不應該發生的狀況。
  • 避免把需要執行的代碼放到斷言中
  • 用斷言來註解並驗證前條件和後條件
  • 對於高健壯性的代碼,應該先使用斷言再處理錯誤

對來源於內部系統的可靠的數據使用斷言,而不要對外部不可靠的數據使用斷言,對於外部不可靠數據,應該使用錯誤處理代碼。斷言可以看成可執行的註釋。

系統外部的數據(用戶輸入,文件,網絡讀取等等)都是不可信的,需要嚴格檢查(通常是錯誤處理)才能放行到系統內部,這相當於一個守衛。

而對於系統內部的交互(比如子程序調用),如果每次也都去處理輸入的數據,也就相當於系統沒有可信的邊界了,會讓代碼變的臃腫複雜;而事實上,在系統內部,傳遞給子程序預期的恰當數據應該是調用者的責任,系統內的調用者應該確保傳遞給子程序的數據是恰當可以正常工作的。這樣一來,就隔離了不可靠的外部環境和可靠的系統內部環境,降低複雜度。

但是在開發階段,代碼極可能包含缺陷,也許是處理外部數據的程序考慮的不夠周全,也許是調用系統內部子程序的代碼存在錯誤,造成子程序調用失敗。這個時候,斷言就可以發揮作用,用來確診到底是那部分出現了問題而導致子程序調用失敗。在清理了所有缺陷之後,內外有別的信用體系就建立起來。等到發行版時候,這些斷言就應該沒有存在必要。

iOS

在iOS開發中,可以使用宏NSAssert()在程序中進行斷言處理。NSAssert()使用正確,可以幫助開發者儘快定位BUG。開發者沒有必要在應用程序的每個版本中都進行斷言檢查,這是因爲大多數項目都是有兩個版本:Debug版和Release版。在Debug版中,開發者希望所有的斷言都檢查到,而在Release版中,往往都是禁用斷言檢查的。

NSAssert()是這樣定義的:

 #define NSAssert(condition, desc)

condition是條件表達式,值爲YES或NO;desc爲異常描述,通常爲NSString。當conditon爲YES時程序繼續運行,爲NO時,則拋出帶有desc描述的異常信息。NSAssert()可以出現在程序的任何一個位置。

設置Release版本中禁用斷言的方法如下:
在Build Settings菜單,找到Preprocessor Macros項,Preprocessor Macros項下面有一個選擇,用於程序生成配置:Debug版和Release版。選擇 Release項,設置NS_BLOCK_ASSERTIONS,不進行斷言檢查。如下圖所示。

斷言.png

NSAssert和assert區別

NSAssertassert都是斷言,主要的差別是assert在斷言失敗的時候只是簡單的終止程序,而NSAssert會報告出錯誤信息並且打印出來.所以只使用NSAssert就好,可以不去使用assert

NSAssert / NSCAssert

iOS中用的最多的是兩對斷言, NSAssert/NSCAssertNSParameterAssert/NSCparameterAssert. 要知道他們的區別,我們先來看看他們定義.

#if !defined(NS_BLOCK_ASSERTIONS)
    #if !defined(_NSAssertBody)
    #define NSAssert(condition, desc, ...)  \\\\
    do {              \\\\
    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \\\\
    if (!(condition)) {       \\\\
    [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \\\\
    object:self file:[NSString stringWithUTF8String:__FILE__] \\\\
    lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \\\\
    }             \\\\
    __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \\\\
    } while(0)
    #endif
    #if !defined(_NSCAssertBody)
    #define NSCAssert(condition, desc, ...) \\\\
    do {              \\\\
    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \\\\
    if (!(condition)) {       \\\\
    [[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \\\\
    file:[NSString stringWithUTF8String:__FILE__] \\\\
    lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \\\\
    }             \\\\
    __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \\\\
    } while(0)
    #endif

從定義可以看出來,前者是適合於Objective-C的方法,_cmdself 與運行時有關. 後者是適用於C的函數。
NSParameterAssert/NSCparameterAssert 兩者的區別也是前者適用於Objective-C的方法,後者適用於C的函數。
實際開發中就用前者就可以了。

NSAssert/NSCAssertNSParameterAssert/NSCparameterAssert 的區別是前者是針對條件斷言, 後者只是針對參數是否存在的斷言, 調試時候可以結合使用,先判斷參數,再進一步斷言,確認原因.

NSAssert的用法

int a = 1;
    // 第一個參數是條件,如果第一個參數不滿足條件,就會記錄並打印後面的字符串
    NSCAssert(a == 2, @"a must equal to 2"); 

運行則會崩潰並在控制檯輸出信息如下:

*** Assertion failure in -[ViewController viewDidLoad](), /Users/user/Desktop/MYAssert/MYAssert/ViewController.m:32
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'a must equal to 2'

NSParameterAssert的用法

- (void)assertWithPara:(NSString *)str {
    // 只需要一個參數,如果參數存在程序繼續運行,如果參數爲空,則程序停止打印日誌
   NSParameterAssert(str); 
   // further code ...
}

如果 調用方法 assertWithPara: 傳入參數爲空則有如下日誌

*** Assertion failure in -[ViewController assertWithPara:], /Users/user/Desktop/MYAssert/MYAssert/ViewController.m:45
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: str'

自定義NSAssertionHandler

NSAssertionHandler實例是自動創建的,用於處理錯誤斷言。如果 NSAssertNSCAssert條件評估爲錯誤,會向 NSAssertionHandler實例發送一個表示錯誤的字符串。每個線程都有它自己的NSAssertionHandler實例。
我們可以自定義處理方法,從而使用斷言的時候,控制檯輸出錯誤,但是程序不會直接崩潰。


#import "MyAssertHandler.h"

@implementation MyAssertHandler

// 處理Objective-C的斷言
- (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,... {
    NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%li", NSStringFromSelector(selector), object, fileName, (long)line);
}
//處理C的斷言
- (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,... {
    NSLog(@"NSCAssert Failure: Function (%@) in %@#%li", functionName, fileName, (long)line);
}

@end

給線程添加處理類

NSAssertionHandler *myHandler = [[MyAssertHandler alloc] init];
// 給當前的線程
[[[NSThread currentThread] threadDictionary] setValue:myHandler
forKey:NSAssertionHandlerKey];

自定義NSAssertionHandler後,程序能夠獲得斷言失敗後的信息,但是程序可以繼續運行,不會強制退出程序.

參考:

斷言_(程式)

斷言(NSAssert)的使用

iOS開發中斷言的使用—NSAssert()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章