1. NSAssert 斷言(NSAssert)是一個巨集,在開發過程中使用NSAssert可以及時發現程式中的問題。 NSAssert聲明如下: #define NSAssert(condition, desc, ...) condition:條件表達式。條件成立時,運行後面程式;不成立時,拋出帶有 ...
1. NSAssert
斷言(NSAssert)是一個巨集,在開發過程中使用NSAssert
可以及時發現程式中的問題。
NSAssert
聲明如下:
#define NSAssert(condition, desc, ...)
- condition:條件表達式。條件成立時,運行後面程式;不成立時,拋出帶有
desc
描述的異常信息。 - desc:異常描述,通常為
NSString
類型對象。用於描述條件表達式不成立的錯誤信息和參數的占位符。 - ...:
desc
字元串中的參數。
假設我們需要判斷變數值是否大於5,我們可以用如下代碼進行判斷。
int i = 6; NSAssert(i>5, @"i must bigger than 5");
運行後,控制台沒有任何輸出。
把變數i
的值改為2
,如下所示:
int i = 2; NSAssert(i>5, @"i must bigger than 5");
運行demo,demo會崩潰。在控制台輸出如下信息:
*** Assertion failure in -[ViewController viewDidLoad], /Users/ad/Library/Mobile Documents/com~apple~CloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m:23
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'i must bigger than 5'
通過控制台輸出的信息,可以得到崩潰發生於:ViewController
類的viewDidLoad
方法中,該文件位於/Users/ad/Library/Mobile Documents/comappleCloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m,導致崩潰的代碼位於第23行,崩潰原因為:i must bigger than 5。
使用NSAssert
時可以對輸出信息進行傳值。
int i = 2; NSAssert1(i>5, @"The real value is %i", i);
輸出為:
*** Assertion failure in -[ViewController viewDidLoad], /Users/ad/Library/Mobile Documents/com~apple~CloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m:23 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The real value is 2'
NSAssert
用於Objective-C,NSCAssert
用於C語言中。
NSAssert1
的desc
帶有一個參數,NSAssert2
的 desc
帶有兩個參數,……,NSAssert5
帶有五個參數。斷言可以帶有零至五個參數。
也許你會好奇為什麼desc
中不使用[NSString stringWithFormat:...]
格式,而要有五個NSAssert
?因為NSAssert()的實現就是一個巨集,因此,要處理異常信息中不同數量參數,就要有多個巨集,所以就有了
NSAssert(condition, dest)
、NSAssert1(condition, formatDest, arg1)
、NSAssert2(condition, formatDest, arg1, arg2)
...NSAssert5(...)
。
NSAssert
和NSLog
都可以在控制台輸出,但NSAssert
輸出後程式立即 crash,控制台也會輸出程式遇到錯誤的位置等信息,而NSLog
只用於輸出信息。
2. NSParameterAssert
如果需要判斷傳入參數是否符合要求,可以使用NSParameterAssert
。
- (NSString *)processItem:(NSUInteger)index { NSParameterAssert(index<self.myArray.count); // do something else }
如果傳入參數index
大於myArray
數組內元素數量,則程式會崩潰。控制它輸出如下:
*** Assertion failure in -[ViewController processItem:], /Users/ad/Library/Mobile Documents/com~apple~CloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m:31 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: index<self.myArray.count'
崩潰信息中會告訴你哪一行代碼出錯了,崩潰原因是什麼。
NSParameterAssert
用於Objective-C,NSCParameterAssert
用於C語言中。
3. 斷言預設只存在於debug版
從Xcode 4.2開始,release 版預設關閉了斷言。也就是當編譯發佈版時,任何調用NSAssert
的地方都被空行替換。所以,不要在NSAssert
內執行任何有效操作。
如果想要在發佈版中使用NSAssert
,可以在 Build Setting 中的 Enable Foundation Assertions 中修改。
4. NSError
NSError
應該用在不是編程錯誤所產生的error,如文件未找到一類非致命性錯誤。你可以把錯誤信息發送給調用者,調用者會進行處理並繼續執行,也可以用警報控制器展現給用戶。
總結
對來源於系統內部的可靠數據使用斷言,即用斷言來處理絕不應該發生的情況。不要對外部不可靠數據(如用戶輸入、文件、網路讀取等)使用斷言,即對於外部不可靠數據或預期會發生的情況應當使用錯誤處理。同時要避免把需要執行的代碼放到斷言中,斷言可以看成可執行的註釋。
來源於系統外部的數據都是不可信的,需要嚴格檢查(通常是錯誤處理)才能放行到系統內部,這相當於一個守衛。而對於系統內部的交互(如調用其他方法),如果每次也都要去處理輸入的數據,也就相當於系統沒有可信的邊界,會讓代碼變的臃腫。事實上,在系統內部傳遞給方法正確的參數是調用者的責任,調用者應該確保傳遞給所調用方法的數據是符合要求的。這樣就隔離了不可靠的外部環境和可靠的內部環境,降低複雜度。
但在開發階段,代碼極可能存在缺陷,有可能是處理外部數據的邏輯不周全或調用內部方法的代碼存在錯誤,最終造成調用失敗。這個時候,斷言就可以發揮作用,用來確診到底哪一部分問題導致程式出錯。在清理了所有缺陷後,內外有別的信用體系就建立起來了。等到發行版的時候,這些斷言就沒有存在的必要了。
單元測試可能是一個更好的方法,但有些情況下(如複雜的演算法過程中),我們希望在代碼中執行檢查,這時斷言將更有效。
參考資料:
作者:pro648