1. 程式人生 > >斷言(NSAssert)在IOS中的應用

斷言(NSAssert)在IOS中的應用

NSAssert()是一個巨集,用於開發階段除錯程式中的Bug,通過為NSAssert()傳遞條件表示式來斷定是否屬於Bug,滿足條件返回真值,程式繼續執行,如果返回假值,則丟擲異常,並且可以自定義異常描述。

檢視原始碼我們可以看到NSAssert是這樣定義的,NSAssert(condition,description),condition取相反值.比如判斷一個值不能為空時,NSAssert(param!=nil,@"param 不能為空");

#define NSAssert(condition, desc, ...)	\
    do {				\
	__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
	if (__builtin_expect(!(condition), 0)) {		\
            NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
            __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
	    [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
		object:self file:__assert_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 (__builtin_expect(!(condition), 0)) {		\
            NSString *__assert_fn__ = [NSString stringWithUTF8String:__PRETTY_FUNCTION__]; \
            __assert_fn__ = __assert_fn__ ? __assert_fn__ : @"<Unknown Function>"; \
            NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
            __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
	    [[NSAssertionHandler currentHandler] handleFailureInFunction:__assert_fn__ \
		file:__assert_file__ \
	    	lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
	}				\
        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
    } while(0)
#endif

NSAssert和assert 區別

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

NSAssert/NSCAssert

iOS中用的最多的是兩對斷言, NSAssert/NSCAssert 和 NSParameterAssert/NSCparameterAssert. 要知道他們的區別,我們先來看看他們定義(如上圖).

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

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

NSAssert的用法

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

執行則會崩潰並在控制檯輸出資訊如下:

*** Assertion failure in 
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'a must equal to 2'

NSParameterAssert的用法

- (void)assertWithPara:(NSString *)str
{
    NSParameterAssert(str); //只需要一個引數,如果引數存在程式繼續執行,如果引數為空,則程式停止列印日誌
    //further code ...
}

如果 呼叫方法 assertWithPara: 傳入引數為空則有如下日誌

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: str'

日誌中的數字是告訴你 第多少行程式碼出錯了。

Xcode 已經預設將release環境下的斷言取消了, 免除了忘記關閉斷言造成的程式不穩定. 所以不用擔心在開發時候大膽使用。

自定義NSAssertionHandler

NSAssertionHandler例項是自動建立的,用於處理錯誤斷言。如果 NSAssert和NSCAssert條件評估為錯誤,會向 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];