错误处理恐怕是开发过程中最让人难受的环节之一。在正常情况下让程序的各部分都正常工作已经是比较困难的了,一个好的应用应该在出错时也有得体的表现。
1. 错误处理模式
三种模式
1.1预期错误
磁盘空间不足是一种预期错误,虽然它极少发生,iTunes在用户设备中装满了音乐文件,这种情况很容易发生。当无法写入文件时,需要能够优雅的恢复
对于那些会预期发生的错误,应该得体的进行处理,不应该让程序崩溃。处理这种错误,比较通用的方式是返回一个NSError对象的引用
1.2程序错误
-(void)doSomething:(NSUInteger)index
{
if (index> self.maxIndex)
{
return;
}
}
传入一个超出有效范围的索引值是一种编程错误,这段代码默默忍受了这种错误,没有做出任何操作。这种问题非常难于调试。应该传入有效的值,这是方法调用者的责任。默默忽略错误值是NSArray能做的最糟糕的事情,让程序崩溃会是一个更好的选择
1.3非预期错误
在iOS中,分配小块内存失败是一种非预期错误,在正常操作中永远不该发生。在遇到这个错误之前,程序应该早就收到内存预警消息并且被强制结束了。
NSString * string =[NSString stringWithFormat:@"%d",1];
NSArray * array = [NSArray arrayWithObject:string];
由于Foundation无法正确分配内存而导致stringWithFormat:方法失败,这种情况是可能发生的。这样会导致程序会抛出一个”试图向数组中插入nil”的异常,最后很可能导致应用崩溃。C语言编程一般会加一个检查来防止这种情况发生,iOS不需要这样。
通常情况下可以忽略非预期错误,直接让程序崩溃就好了。理由:发生概率极小;保持代码简洁;OC中为内存分配错误处理代码几乎是没有意义。
2. 断言
使用断言可以有效的防止程序错误。断言要求程序中特定的语句必须为真。如果不为真,说明程序正处于一种无法预测的状态,这时候程序不应该继续执行下去。
NSAssert( index != 0 , @"indexmust not be zero");
[self doSomething:0];
控制台打印:
2013-07-13 10:31:14.466 test[8796:c07] *** Assertionfailure in -[WGQ_ViewController doSomething:],/Users/king/Desktop/test/test/WGQ_ViewController.m:26
2013-07-13 10:31:14.467 test[8796:c07] *** Terminatingapp due to uncaught exception 'NSInternalInconsistencyException', reason:'index must not be zero'
如果测试条件返回NO,NSAssert 就会抛一个异常,异常处理程序捕获异常之后,会调用abort结束程序。 并显示自定义的消息"index must not be zero"
。并同时显示出错文件,出错代码,调用函数等消息。他是一个程序跟踪的很好的手段。(C语言中使用断言NSCAssert.)
MAC开发,出现异常只会结束当前循环,iOS中,结束整个程序。
禁用NSAssert: targets – build setting –APPLE LLVM—release 中添加NS_BLOCK_ASSERTIONS.
XCode 4.6.3 默认情况下会禁用发布版本代码中得断言。
用法总结与注意事项:
1)在函数开始处检查传入参数的合法性
2) 每个NSAssert 只检验一个条件,因为检验多个条件时,当断言失败,无法直观的判断是哪个条件失败
3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题 错误:
assert(i++< 100) 这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
正确: assert(i < 100) ;
i++;
4) assert和后面的语句应空一行,以形成逻辑和视觉上的一致感
5)有的地方,assert不能代替条件过滤
下面的代码是吧NSCAssert 包装为RNCAssert 在OC中使用断言的时候就应该使用NSAssert。
#define RNLogBug NSLog
// RNAssert and RNCAssert work exactly like NSAssert andNSCAssert
// except they log, even in release mode
#define RNAssert(condition, desc, ...) \
if (!(condition)){ \
RNLogBug((desc), ## __VA_ARGS__); \
NSAssert((condition),(desc), ## __VA_ARGS__); \
}
#define RNCAssert(condition, desc) \
if (!(condition)){ \
RNLogBug((desc), ## __VA_ARGS__); \
NSCAssert((condition), (desc), ## __VA_ARGS__); \
}
断言应该位于导致程序崩溃的代码之前。看下面的例子
RNAssert(foo !=nil,@"foo must not be nil");
[array addObject:foo];
如果这里会导致断言失败,那么关闭断言,程序依然会崩溃。所以要将代码改为下面这样:
RNAssert(foo != nil, @"foomust not be nil");
if (foo != nil) {
[array addObject:foo];
}
这样就好了,RNAssert 可以记录日志。但是代码有冗余,如果断言条件和if条件不匹配,就可能产生bug
if (foo != nil)
{
[arrayaddObject:foo];
}
else
{
RNAssert(NO, @"foo must not be nil");
}
这样就保证了断言条件跟if条件总是匹配的。这是一种比较好的断言使用方式。另外建议在switch语句的defualt分支中使用断言。
assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
插一点关于调试信息—几个宏 (附加内容):
1)__VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错, 你可以试试。
2) __FILE__ 宏在预编译时会替换成当前的源文件名
3) __LINE__宏在预编译时会替换成当前的行号
4)__FUNCTION__宏在预编译时会替换成当前的函数名称
3. 异常
简单的说,在OC中,异常不是用来处理那些可恢复的错误的。异常是用来处理那些永远不应该发生却却发生了的错误,而这个时候应该结束程序运行。异常跟NSAssert比较像,事实上,NSAssert 就是作为异常实现的。
[NSException raise:@"WebService error" format:@"%@",@"测试111111"];
程序抛出异常的原因多种多样,可由硬件导致也可由软件引起。异常的例子很多,包括被零除、下溢和上异之类的数学错误,调用未定义的指令(例如,试图调用一个没有定义的方法 )以及试图越界访问群体中的元素
网上找的例子:
NSException* ex = [[NSExceptionalloc]initWithName:@"MyException" reason:@"b==0" userInfo:nil];
@try
{
int b = 0;
switch (b)
{
case 0:
@throw(ex);//b=0,则抛出异常;
break;
default:
break;
}
}
@catch (NSException *exception)//捕获抛出的异常
{
NSLog(@"%@~~~%@",exception.name,exception.reason);
NSLog(@"b==0 Exception!");
}
@finally
{
NSLog(@"finally!");
}
[exrelease];
典型应用:
@try {
a = UIApplicationMain(argc, argv,nil,NSStringFromClass([PGAppDelegate class]));
}
@catch(NSException *exception) {
NSLog(@"Caught %@%@", [exception name], [exception reason]);
NSLog(@"Exception - %@",[exception callStackSymbols]);
exit(EXIT_FAILURE);
}
return a;
以下内容出自 Exception Programming Topics :
Cocoa框架通常不是异常安全的。异常只用来处理程序员犯的错误,程序捕获到这种异常之后应该尽快退出运行。
在OC中,ARC默认情况不是异常安全的,有可能是因为异常而产生严重的内存泄露。理论上来说,OC++ 中得ARC是异常安全的。但是@autoreleasepool块任然可能导致后台线程发生内存泄露。使用异常安全的ARC会导致性能下降,这也是应该避免大量使用OC++的原因之一。在Clang中,制定-fobjc-arc-exception 编译标志就可使用异常安全的ARC。