iOS - 断言处理与调试
来源:互联网 发布:有趣的名字知乎 编辑:程序博客网 时间:2024/05/22 04:30
一、Objective-C 中的断言:
- Objective-C 中的断言处理使用的是 NSAssertionHandler :
每个线程拥有它自己的断言处理器,它是 NSAssertionHandler 类的实例对象。当被调用时,一个断言处理器打印一条包含方法和类名(或者函数名)的错误信息。然后它抛出一个 NSInternalInconsistencyException 异常。
- 基础类中定义了两套断言宏
- 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)
- NSAssert / NSCAssert
#小心使用NSAssert,可以看到它的定义中出现了一个self, 有可能在你的block中你会发现你明明没有self的strong引用,但是仍然出现了循环引用。就看看你是否使用了NSAssert,这个宏被展开之后持有了self,那么有可能就会出现引用不释放的问题。
而使用NSCassert,就不会有这样的问题了。因为它定义使用的handleFailureInFunction函数,并没有self引用。
- 这么做的意义在于两点:
- 第一个是苹果对于断言处理在 API 层面进行了区分:
NSAssert
与NSCAssert
用来处理一般情况的断言NSParameterAssert
与NSCParameterAssert
用来处理参数化的断言
- 第二个是区别是在 Objective - C 和 C 之间进行了区分这样才有了:
NSAssert
与NSCAssert
NSParameterAssert
与NSCParameterAssert
- 第一个是苹果对于断言处理在 API 层面进行了区分:
二、使用 NSAssertionHandler
- 从 Xcode 4.2 开始,发布构建默认关闭了断言,它是通过定义 NS_BLOCK_ASSERTIONS 宏实现的。也就是说,当编译发布版时,任何调用 NSAssert 等的地方都被有效的移除了。
尽管基础类库的断言宏在它们自己的权力下十分有用————虽然只用于开发之中。NSAssertionHandler 还提供了一套优雅地处理断言失败的方式来保留珍贵的现实世界的使用信息。
Pay Attension:
据说,许多经验丰富的 Objective-C 开发者们告诫不要在生产环境中使用 NSAssertionHandler。基础类库中的断言处理是用来在一定安全距离外来理解和感激的。请小心行事如果你决定在对外发布版的应用中使用它。
NSAssertionHandler
是一个很直接的类,带有两个需要在子类中实现的方法:-handleFailureInMethod:...
(当 NSAssert / NSParameterAssert 失败时调用)和-handleFailureInFunction:...
(当 NSCAssert / NSCParameterAssert 失败时调用)。- 接下来看一个使用的实例
#pragram 第一步,创建一个继承自NSAssertionHandler 的类:LoggingAssertionHandler 用来专门处理断言#import <Foundation/Foundation.h>@interface LoggingAssertionHandler : NSAssertionHandler@end#import "LoggingAssertionHandler.h"@implementation LoggingAssertionHandler/** 重写两个失败的回调方法,在这里执行我们想要抛出的错误(打印或者直接报错) */- (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
- 每个线程都可以指定断言处理器。 想设置一个 NSAssertionHandler 的子类来处理失败的断言,在线程的threadDictionary 对象中设置 NSAssertionHandlerKey 字段即可。
大部分情况下,你只需在
-application:didFinishLaunchingWithOptions:
中设置当前线程的断言处理器。
- AppDelegate 中的处理
- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions{NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];[[[NSThread currentThread] threadDictionary] setValue:assertionHandler forKey:NSAssertionHandlerKey];// ...return YES;}
- 这样我们就完成再当前线程中使用我们自定义的断言处理器的配置,那么接下来,如果有和我们条件不同的情况都直接会回调对应着的那两个失败的方法,我们可以在那俩个方法中按自己的输出意愿来处理你的话术。
- 具体应用
根据输出情况可以看到是完全按照我们所需要的来输出的#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; NSObject*mc = [NSObject new]; mc = @2; NSAssert(mc == nil, @"我不为空了");}@end
2015-10-30 21:33:14.529 NSAssert[20537:678428] *** Terminating app due to uncaught exception 'viewDidLoad', reason: '我不为空了'
三、使用上的注意点
- 仔细观察 NSAssert 的宏定义 ,你会发现 self 的痕迹,有 self 的地方就一定要注意 block 容易产生的循环引用问题。
/** 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
- 接下来举个例子:
这样我们就会看到 Block 中循环引用的警告啦:/** 创建一个 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
那如果我想在 Block 中使用断言怎么办呐?用NSCAssert
替换NSAssert
,NSCParameterAssert
来替换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); };}
- 哦了。
0 0
- iOS - 断言处理与调试
- Java异常处理与断言
- iOS 使用断言NSAssert()调试程序错误
- iOS 使用断言NSAssert()调试程序错误
- iOS开发Foundation中的断言处理
- Java异常、断言、日志与调试
- java之断言、测试(JUnit)与调试
- 断言调试
- 断言 IOS
- iOS断言
- 断言与静态断言
- [IOS 开发] 使用断言NSAssert()调试程序错误
- iOS 断言 NSAssert的使用 调试程序错误
- swift 断言调试
- SoapUI利用Groovy对response与断言的处理
- Java编程手册—异常处理与断言
- C/C++学习笔记八(断言与异常处理)
- 动态断言与静态断言
- Android 反射实战 - 更换APP皮肤<1>
- VS2010为所有的工程配置lib和include路径
- 倒计时自动关闭弹出窗口
- <<UNIX环境高级编程>>随书代码的打开方式
- 认识DOM的三大节点:元素节点,文本节点,属性节点以及nodeName,nodeType,nodeValue的区别
- iOS - 断言处理与调试
- VC++中的通知消息
- 结构(公有类)的运算符重载
- ORACLE表空间的创建修改删除
- 在Android中操作XML数据-读取与解析XML数据/生成与输出XML数据
- document 获得元素节点,属性节点,文本节点。
- 求C++数据结构二叉树的宽度
- 数据库连接操作
- CodeForces 507E Breaking Good 最短路