Objective-C 错误(Error)与异常(Exception)

687 阅读5分钟

几乎每一个APP都会遇到error. 其中一些error我们无法控制, 比如硬盘空间不足或者失去网络连接, 还有一些错误是可以恢复的, 比如无效的用户输入. 虽然所有的开发者对于程序都会力争完美, 但是偶尔也会产生程序错误.

当我们使用Objective-C时, exception完全是用来处理程序错误的, 比如数组越界或者无效的方法参数. 这些问题应当在App打包上架之前被找到并修复.

所有的其它错误都是使用NSError类实例来表示的. 这篇文章简要介绍NSError对象, 通过framework中的一些可能失败并返回error的方法来举例介绍. 更多信息, 详见: Error Handling Programming Guide.

使用NSError获取错误信息

Error是App生命周期中不可避免的一部分. 比如当我们需要从远程Web服务请求数据时,就有大量可能产生的潜在问题:

  • 没有网络连接
  • 远程Web服务不可用
  • 远程Web服务无法提供我们请求的信息
  • 从远程获取到的数据不符合我们的期望

可惜的是, 我们没有办法对于每一个可能想到的问题都构建一个应急的方案. 因此, 我们必须计划好如何处理错误以提供最佳的用户体验.

当我们使用framework类实现特定功能时, 常常使用代理对象. 比如从远程Web服务中下载信息时, 我们需要实现NSURLConnectionDelegate 协议, 这个协议中就包含一个connection:didFailWithError: 方法:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

当error发生时, 这个代理方法将被调用, 错误信息通过NSError对象传递出来.

一个NSError对象包含一个数字类型的错误代码, domain, 描述,以及放置在一个user info dictionary中的其它相关的信息.

Cocoa 和 Cocoa Touch error 将错误分成不同的domain. 如果错误在NSURLConnection中发生了, connection:didFailWithError: 中就会返回一个domain值为NSURLErrorDomain的错误. 这个错误还会包含一个描述信息: 比如: “A server with the specified hostname could not be found.” -- 无法找到指定host名称的服务器.

这样, 通过domainerror code 我们就可以确定问题的具体原因, 并通过 description 获取error的描述信息.

还有一些方法通过引用传递错误. 比如当我们尝试将NSData数据存储到文件中: writeToURL:options:error: . 方法的最后一个参数就是一个指向NSError指针的引用:

- (BOOL)writeToURL:(NSURL *)aURL
           options:(NSDataWritingOptions)mask
             error:(NSError **)errorPtr;

在调用这个方法前, 我们需要创建一个合适的指针来传递指针的地址:

    NSError *anyError;
    BOOL success = [receivedData writeToURL:someLocalFileURL
                                    options:0
                                      error:&anyError];
    if (!success) {
        NSLog(@"Write failed with error: %@", anyError);
        // present error to user
    }

当错误发生时, writeToURL:... 方法将会返回NO, 并使我们传入的anyError指针指向一个错误对象.

当处理通过引用传递的指针时, 通过方法返回值判断error是否发生是很重要的, 不要只是判断error指针是否指向了一个错误.

Tip: 如果我们不需要处理错误, 可以向error:参数中传递NULL.

当App可以遇到错误时, 如果可以从错误中恢复, 对于用户体验是最好的. 如果我们进行远程Web请求, 当遇到错误时, 我们或者可以尝试重新从另一个服务器获取数据.

如果我们无法从错误中恢复时, 我们应当通过Alert 或者Toast之类的方式提示用户.

对于更多如何向用户展示错误信息, 详见: Displaying Information From Error Objects.

当然, 我们也可以自定义NSError对象. 为此, 我们需要首先定义error的domain,形式可能如下:

com.companyName.appOrFrameworkName.ErrorDomain

我们也需要为每个可能产生的错误挑选一个独特的错误代码以及合适的描述信息:

    NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
    NSString *desc = NSLocalizedString(@"Unable to…", @"");
    NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
 
    NSError *error = [NSError errorWithDomain:domain
                                         code:-101
                                     userInfo:userInfo];

当我们需要抛出错误时:

- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr;
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr {
    ...
    // error occurred
    if (errorPtr) {
        *errorPtr = [NSError errorWithDomain:...
                                        code:...
                                    userInfo:...];
    }
    return NO;
}

我们需要先判断errorPtr 是否传递了非NULL的指针之后再解引用.

使用NSException处理程序错误

Objective-C 对于异常的支持和其它语言差不多, 和Java或者C++的语法很类似. Cocoa 和 Cocoa Touch 中的异常也是一个对象, 使用NSException类表示.

如果我们需要编写可能抛出异常的代码, 我们可以使用try-catchblock:

    @try {
        // do something that might throw an exception
    }
    @catch (NSException *exception) {
        // deal with the exception
    }
    @finally {
        // optional block of clean-up code
        // executed whether or not an exception occurred
    }

@try代码块中的代码抛出了异常, @catch将会捕获到这个异常.

如果异常抛出, 但是并没有被捕获. 默认的行为就是在控制台输出相关异常信息之后,结束程序.

对于基本的Objective-C方法我们不应当使用try-catchblock捕获异常. 拿NSArray举例来说, 我们应该总是检查array中元素的数量来避免越界. 我们应当在正式版本中尽量避免抛出异常.

对于更多Objective-C应用的异常信息, 详见Exception Programming Topics.

参考资料: Dealing with Errors