概述
我们在阅读一些有名的源码时,经常会碰到这样的编写:
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
在AFNetworking/AFURLSessionManager.m文件中,有这么一个方法,方法的实现的顶部出现这样的代码:NSParameterAssert(task),这其实就是断言宏处理。
基础类中定义了两套断言宏:
- NSAssert / NSCAssert
#if (defined(__STDC_VERSION__) && (199901L <= __STDC_VERSION__)) || (defined(__cplusplus) && (201103L <= __cplusplus))
#if !defined(NS_BLOCK_ASSERTIONS)
#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
#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
#else // NS_BLOCK_ASSERTIONS defined
#if !defined(_NSAssertBody)
#define NSAssert(condition, desc, ...) do {} while (0)
#endif
#if !defined(_NSCAssertBody)
#define NSCAssert(condition, desc, ...) do {} while (0)
#endif
#endif
- NSParameterAssert / NSCParameterAssert
#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
#define NSCParameterAssert(condition) NSCAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
- NSAssert / NSCAssert 是用来处理一般情况的断言。
- NSParameterAssert / NSCParameterAssert 是用来处理参数的断言。
- NSAssert / NSParameterAssert 是用来处理Objective - C中的断言。
- NSCAssert / NSCParameterAssert 是用来处理C中的断言。
使用 NSAssertionHandler
FOUNDATION_EXPORT NSString * const NSAssertionHandlerKey NS_AVAILABLE(10_6, 4_0);
@interface NSAssertionHandler : NSObject {
@private
void *_reserved;
}
+ (NSAssertionHandler *)currentHandler;
- (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(nullable NSString *)format,... NS_FORMAT_FUNCTION(5,6);
- (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(nullable NSString *)format,... NS_FORMAT_FUNCTION(4,5);
@end
从官方API可以看出,NSAssertionHandler 是一个很直接的类,需要在子类中实现其中的两个方法:
#import <Foundation/Foundation.h>
@interface RPAssertionHandler : NSAssertionHandler
@end
#import "RPAssertionHandler.h"
@implementation RPAssertionHandler
/* 重写Objective - C中失败的回调的方法,写出我们自己想要的错误展示方式 */
- (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ... {
NSString *selectorStr = NSStringFromSelector(selector);
NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%ld", selectorStr, object, fileName ,line);
NSException *exception = [NSException exceptionWithName:selectorStr reason:format userInfo:nil];
@throw exception;
}
/* 重写C中失败的回调的方法,写出我们自己想要的错误展示方式 */
- (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ... {
NSLog(@"NSCAssert/NSCParameterAssert failure in Function: %@", functionName);
}
@end
每个线程都可指定断言处理器,如想使用我们自己写的子类处理断言,只需要在当前线程中设置线程threadDictionary字典对象的NSAssertionHandlerKey字段。当然,大部分情况下,你只需在 - application:didFinishLaunchingWithOptions:中设置当前线程的断言处理器就行了。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
Class RPAssertionHandlerClass = NSClassFromString(@"RPAssertionHandler");
NSAssertionHandler *assertionHandler = [[RPAssertionHandlerClass alloc] init];
NSDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
// threadDictionary[NSAssertionHandlerKey] = assertionHandler;
// KVC
[threadDictionary setValue:assertionHandler forKey:NSAssertionHandlerKey];
return YES;
}
接下来我们使用一下:
- (void)updateData:(NSData *)data {
NSParameterAssert(data);
}
- (void)updateCString:(const char *)cString {
NSCParameterAssert(cString);
}
可以看出输出情况完全是按照我们所期望的打印出来的。注意事项
我们如果仔细观看NSAsset得宏定义,你会发现有self存在,我们知道有self的地方就必须注意在使用block时循环引用问题。如果不得已必须在block中使用断言,用NSCAssert / NSCParameterAssert 代替 NSAssert / NSParameterAssert的使用。
;