概述
我们在阅读一些优秀的源码时,可能会遇到使用 NSParameterAssert 宏的例子,在AFNetworking/AFURLSessionManager.m 文件中,有这么一个方法,方法的实现的顶部出现这样的代码:NSParameterAssert(task),这其实就是断言宏处理。
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task); // 断言宏处理
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
基础类中定义了两套断言宏:
NSAssert/NSCAssertNSParameterAssert/NSCParameterAssert
那么,它们之间都有什么区别和联系呢?
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 的使用。