错误处理恐怕是开发过程中最让人难受的环节之一。在正常情况下让程序的各部分都正常工作已经是比较困难的了,一个好的应用应该在出错时也有得体的表现。

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];

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'

"index must not be zero"

。并同时显示出错文件,出错代码,调用函数等消息。他是一个程序跟踪的很好的手段。(C语言中使用断言NSCAssert.)

    MAC开发,出现异常只会结束当前循环,iOS中,结束整个程序。

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");
addObject:foo];

如果这里会导致断言失败,那么关闭断言,程序依然会崩溃。所以要将代码改为下面这样:

RNAssert(foo !=  nil, @"foomust not be nil");
if (foo !=  nil) {
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。