一直想寫一篇你關于斷言的文章, 今天有時間趕緊寫出來.
參考 Mattt 文章
一、Objective - C 中的斷言:
- Objective - C 中的斷言處理使用的是 NSAssertionHandler :
每個線程擁有它自己的斷言處理器,它是 NSAssertionHandler 類的實例對象。當被調用時,一個斷言處理器打印一條包含方法和類名(或者函數名)的錯誤信息。然后它拋出一個 NSInternalInconsistencyException 異常。
- 基礎類中定義了兩套斷言宏
- NSAssert / NSCAssert
/** NSAssert */
#if !defined(_NSAssertBody)
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
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
/** NSCAssert */
#if !defined(_NSCAssertBody)
#define NSCAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
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
- NSParameterAssert / NSCParameterAssert
/** NSParameterAssert */
#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
/** NSCParameterAssert */
#define NSCParameterAssert(condition) NSCAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
- 這么做的意義在于兩點:
- 第一個是蘋果對于斷言處理在 API 層面進行了區分:
-
NSAssert
與NSCAssert
用來處理一般情況的斷言 -
NSParameterAssert
與NSCParameterAssert
用來處理參數化的斷言
-
- 第二個是區別是在 Objective - C 和 C 之間進行了區分這樣才有了:
-
NSAssert
與NSCAssert
-
NSParameterAssert
與NSCParameterAssert
-
- 第一個是蘋果對于斷言處理在 API 層面進行了區分:
二、使用 NSAssertionHandler
- 從 Xcode 4.2 開始,發布構建默認關閉了斷言,它是通過定義 NS_BLOCK_ASSERTIONS 宏實現的。也就是說,當編譯發布版時,任何調用 NSAssert 等的地方都被有效的移除了。
盡管基礎類庫的斷言宏在它們自己的權力下十分有用————雖然只用于開發之中。NSAssertionHandler 還提供了一套優雅地處理斷言失敗的方式來保留珍貴的現實世界的使用信息。
Pay Attension:
據說,許多經驗豐富的 Objective-C 開發者們告誡不要在生產環境中使用 NSAssertionHandler。基礎類庫中的斷言處理是用來在一定安全距離外來理解和感激的。請小心行事如果你決定在對外發布版的應用中使用它。
-
NSAssertionHandler
是一個很直接的類,帶有兩個需要在子類中實現的方法:-handleFailureInMethod:...
(當 NSAssert / NSParameterAssert 失敗時調用)和-handleFailureInFunction:...
(當 NSCAssert / NSCParameterAssert 失敗時調用)。 - 接下來看一個使用的實例
#pragram 第一步,創建一個繼承自NSAssertionHandler 的類:LoggingAssertionHandler 用來專門處理斷言
#import <Foundation/Foundation.h>
@interface LoggingAssertionHandler : NSAssertionHandler
@end
#import "LoggingAssertionHandler.h"
@implementation LoggingAssertionHandler
/** 重寫兩個失敗的回調方法,在這里執行我們想要拋出的錯誤(打印或者直接報錯) */
- (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);
NSException *e = [NSException
exceptionWithName: NSStringFromSelector(selector)
reason: format
userInfo: nil];
@throw e;
}
- (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 的子類來處理失敗的斷言,在線程的threadDictionary 對象中設置 NSAssertionHandlerKey 字段即可。
大部分情況下,你只需在
-application:didFinishLaunchingWithOptions:
中設置當前線程的斷言處理器。
- AppDelegate 中的處理
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];
[[[NSThread currentThread] threadDictionary] setValue:assertionHandler
forKey:NSAssertionHandlerKey];
// ...
return YES;
}
- 這樣我們就完成再當前線程中使用我們自定義的斷言處理器的配置,那么接下來,如果有和我們條件不同的情況都直接會回調對應著的那兩個失敗的方法,我們可以在那倆個方法中按自己的輸出意愿來處理你的話術。
- 具體應用
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSObject*mc = [NSObject new];
mc = @2;
NSAssert(mc == nil, @"我不為空了");
}
@end
根據輸出情況可以看到是完全按照我們所需要的來輸出的
2015-10-30 21:33:14.529 NSAssert[20537:678428] ***
Terminating app due to uncaught exception 'viewDidLoad', reason: '我不為空了'
三、使用上的注意點
- 仔細觀察 NSAssert 的宏定義 ,你會發現 self 的痕跡,有 self 的地方就一定要注意 block 容易產生的循環引用問題。
/** NSAssert */
#if !defined(_NSAssertBody)
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
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
- 接下來舉個例子:
/** 創建一個 preson 類 */
#import <Foundation/Foundation.h>
typedef void(^mitchelBlock)(int num);
@interface person : NSObject
@property(nonatomic, copy)mitchelBlock block;
@end
#import "person.h"
@implementation person
- (instancetype)init{
if (self = [super init]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.block) {
self.block(1);
}
});
}
return self;
}
@end
/** ViewController 中的代碼 */
#import "ViewController.h"
#import "person.h"
@interface ViewController ()
@property(nonatomic, strong)person * aPerson;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSObject*mc = [NSObject new];
mc = @2;
self.aPerson = [person new];
self.aPerson.block = ^(int num){
NSAssert(mc == nil, @"我不為空了");
NSLog(@"%d",num);
};
}
@end
這樣我們就會看到 Block 中循環引用的警告啦:
屏幕快照 2015-10-30 下午9.48.17.png
那如果我想在 Block 中使用斷言怎么辦吶?用
NSCAssert
替換 NSAssert
,NSCParameterAssert
來替換 NSParameterAssert
- (void)viewDidLoad {
[super viewDidLoad];
NSObject*mc = [NSObject new];
mc = @2;
self.aPerson = [person new];
self.aPerson.block = ^(int num){
NSCAssert(mc == nil, @"我不為空了");
NSCParameterAssert(num>5);
};
}
- 這樣就 OK 了。