Objective - C 断言处理

291 阅读2分钟

概述

我们在阅读一些优秀的源码时,可能会遇到使用 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 / NSCAssert
  • NSParameterAssertNSCParameterAssert

那么,它们之间都有什么区别和联系呢?

  • NSAssert / NSCAssert 是用来处理一般情况的断言。
  • NSParameterAssert / NSCParameterAssert 是用来处理参数的断言。
  • NSAssertNSParameterAssert 是用来处理 Objective - C 中的断言。
  • NSCAssertNSCParameterAssert 是用来处理 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 中使用断言,用 NSCAssertNSCParameterAssert 代替 NSAssertNSParameterAssert 的使用。