一直想寫(xiě)一篇你關(guān)于斷言的文章, 今天有時(shí)間趕緊寫(xiě)出來(lái).
參考 Mattt 文章
一、Objective - C 中的斷言:
- Objective - C 中的斷言處理使用的是 NSAssertionHandler :
每個(gè)線程擁有它自己的斷言處理器罪裹,它是 NSAssertionHandler 類的實(shí)例對(duì)象妙色。當(dāng)被調(diào)用時(shí)娶牌,一個(gè)斷言處理器打印一條包含方法和類名(或者函數(shù)名)的錯(cuò)誤信息。然后它拋出一個(gè) NSInternalInconsistencyException 異常。
- 基礎(chǔ)類中定義了兩套斷言宏
- 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)
- 這么做的意義在于兩點(diǎn):
- 第一個(gè)是蘋(píng)果對(duì)于斷言處理在 API 層面進(jìn)行了區(qū)分:
-
NSAssert
與NSCAssert
用來(lái)處理一般情況的斷言 -
NSParameterAssert
與NSCParameterAssert
用來(lái)處理參數(shù)化的斷言
-
- 第二個(gè)是區(qū)別是在 Objective - C 和 C 之間進(jìn)行了區(qū)分這樣才有了:
-
NSAssert
與NSCAssert
-
NSParameterAssert
與NSCParameterAssert
-
- 第一個(gè)是蘋(píng)果對(duì)于斷言處理在 API 層面進(jìn)行了區(qū)分:
二适室、使用 NSAssertionHandler
- 從 Xcode 4.2 開(kāi)始勿负,發(fā)布構(gòu)建默認(rèn)關(guān)閉了斷言馏艾,它是通過(guò)定義 NS_BLOCK_ASSERTIONS 宏實(shí)現(xiàn)的。也就是說(shuō)奴愉,當(dāng)編譯發(fā)布版時(shí)琅摩,任何調(diào)用 NSAssert 等的地方都被有效的移除了。
盡管基礎(chǔ)類庫(kù)的斷言宏在它們自己的權(quán)力下十分有用————雖然只用于開(kāi)發(fā)之中锭硼。NSAssertionHandler 還提供了一套優(yōu)雅地處理斷言失敗的方式來(lái)保留珍貴的現(xiàn)實(shí)世界的使用信息房资。
Pay Attension:
據(jù)說(shuō),許多經(jīng)驗(yàn)豐富的 Objective-C 開(kāi)發(fā)者們告誡不要在生產(chǎn)環(huán)境中使用 NSAssertionHandler檀头『湟欤基礎(chǔ)類庫(kù)中的斷言處理是用來(lái)在一定安全距離外來(lái)理解和感激的。請(qǐng)小心行事如果你決定在對(duì)外發(fā)布版的應(yīng)用中使用它暑始。
-
NSAssertionHandler
是一個(gè)很直接的類搭独,帶有兩個(gè)需要在子類中實(shí)現(xiàn)的方法:-handleFailureInMethod:...
(當(dāng) NSAssert / NSParameterAssert 失敗時(shí)調(diào)用)和-handleFailureInFunction:...
(當(dāng) NSCAssert / NSCParameterAssert 失敗時(shí)調(diào)用)。 - 接下來(lái)看一個(gè)使用的實(shí)例
#pragram 第一步廊镜,創(chuàng)建一個(gè)繼承自NSAssertionHandler 的類:LoggingAssertionHandler 用來(lái)專門(mén)處理斷言
#import <Foundation/Foundation.h>
@interface LoggingAssertionHandler : NSAssertionHandler
@end
#import "LoggingAssertionHandler.h"
@implementation LoggingAssertionHandler
/** 重寫(xiě)兩個(gè)失敗的回調(diào)方法牙肝,在這里執(zhí)行我們想要拋出的錯(cuò)誤(打印或者直接報(bào)錯(cuò)) */
- (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
- 每個(gè)線程都可以指定斷言處理器。 想設(shè)置一個(gè) NSAssertionHandler 的子類來(lái)處理失敗的斷言期升,在線程的threadDictionary 對(duì)象中設(shè)置 NSAssertionHandlerKey 字段即可惊奇。
大部分情況下,你只需在
-application:didFinishLaunchingWithOptions:
中設(shè)置當(dāng)前線程的斷言處理器播赁。
- AppDelegate 中的處理
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];
[[[NSThread currentThread] threadDictionary] setValue:assertionHandler
forKey:NSAssertionHandlerKey];
// ...
return YES;
}
- 這樣我們就完成再當(dāng)前線程中使用我們自定義的斷言處理器的配置颂郎,那么接下來(lái),如果有和我們條件不同的情況都直接會(huì)回調(diào)對(duì)應(yīng)著的那兩個(gè)失敗的方法容为,我們可以在那倆個(gè)方法中按自己的輸出意愿來(lái)處理你的話術(shù)乓序。
- 具體應(yīng)用
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSObject*mc = [NSObject new];
mc = @2;
NSAssert(mc == nil, @"我不為空了");
}
@end
根據(jù)輸出情況可以看到是完全按照我們所需要的來(lái)輸出的
2015-10-30 21:33:14.529 NSAssert[20537:678428] ***
Terminating app due to uncaught exception 'viewDidLoad', reason: '我不為空了'
三寺酪、使用上的注意點(diǎn)
- 仔細(xì)觀察 NSAssert 的宏定義 ,你會(huì)發(fā)現(xiàn) self 的痕跡,有 self 的地方就一定要注意 block 容易產(chǎn)生的循環(huán)引用問(wèn)題替劈。
/** 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
- 接下來(lái)舉個(gè)例子:
/** 創(chuàng)建一個(gè) 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
這樣我們就會(huì)看到 Block 中循環(huán)引用的警告啦:
那如果我想在 Block 中使用斷言怎么辦吶寄雀?用
NSCAssert
替換 NSAssert
,NSCParameterAssert
來(lái)替換 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 了陨献。