Objective - C 断言处理

本文介绍了Objective-C中断言处理的概念,包括NSAssertionHandler的使用,以及NSAssert、NSCAssert、NSParameterAssert和NSCParameterAssert四组断言宏的区别,重点讲解它们在Objective-C和C语言中的应用及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述


我们在阅读一些有名的源码时,经常会碰到这样的编写:

- (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的使用。

;







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值