Dealing with Errors
几乎所有的程序都会遇到错误。一些错误可能超出了你的控制范围,比如耗尽了内存空间或者失去了网络连接。一些错误是可以避免的,比如用户的无效输入。即使所有的开发人员都是精益求精的,程序设计员偶尔也会发生错误。
如果你之前从事的是其他的平台和语言,你可能习惯使用异常来处理大多数的错误。当你用OC编写代码时,异常仅仅用于程序员错误,比如数组边界溢出的问题或者无效的方法参数。这些问题都是在你的程序上架之前可以发现并且改正的。
其他的错误都是用NSError类表示的。本章主要介绍如何使用NSError对象,包括如何处理框架方法失败和返回错误的问题。详见Error Handling Programming Guide。
Use NSError for Most Errors
在任何应用的生命周期内,错误都是不可避免的。例如,如果你需要从一个远程的web服务请求一个数据,会有各种各样的潜在问题出现,例如:
-
No network connectivity 无网络连接
-
The remote web service may be inaccessible 远程网络服务不可到达
-
The remote web service may not be able to serve the information you request 远程web服务器没有你需要的信息
-
The data you receive may not match what you were expecting 你收到的数据和你需要的不匹配
遗憾的是,你不可能制定一个应急的计划,从而解决每一个可能发生的错误。相反,你只能为错误定制出一个计划,并且知道如何处理她们,从而给出最好的用户体验。
Some Delegate Methods Alert You to Errors
如果你使用一个框架类实现一个委托对象,从而执行一个特定的任务,比如从远程的web服务器下载信息,你会使用一个典型的错误处理方法。NSURLConnectionDelegate协议,包括一个connection:didFailWithError:方法:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; |
如果有一个错误出现,这个代理方法会被调用,从而提供给你一个NSError对象来描述错误的问题。
一个NSError对象包括一个数字错误代码,域名和描述,以及其他的相关信息,都被打包在一个用户信息字典里。
不是为每个可能的错误都分配一个特殊的数字代码,Cocoa和Cocoa Touch中的错误被划分为许多区域。如果一个错误放生在NSURLConnection,上面的connection:didFailWithError:方法就会从NSURLErrorDomain提供一个错误。
错误对象也包含一个局部的描述,比如 “A server with the specified hostname could not be found.”(指定的主机名未被找到。)
Some Methods Pass Errors by Reference
一些Cocoa和Cocoa Touch接口会通过引用来传回错误。例如,你可能需要把一个来自远程web服务器的数据写入到磁盘中,使用NSData方法writeToURL:options:error。方法中的最后一个参数就是一个NSError的指针。
- (BOOL)writeToURL:(NSURL *)aURL |
options:(NSDataWritingOptions)mask |
error:(NSError **)errorPtr; |
Before you call this method, you’ll need to create a suitable pointer so that you can pass its address:
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指针指向一个错误对象来描述这个问题。
当通过引用来处理错误传递时,你需要测试一下方法的返回值,看看返回值是否会引发一个错误,就像上面显示的那样。不要只是测试错误指针是否被设置为指向一个错误。
Recover if Possible or Display the Error to the User
最好的用户体验是将你的应用在一个错误中透明的恢复过来。例如,你发送了一个远程web服务器请求,如果未成功的话你可以试着再请求一次不同的服务器。这一次你可能需要对用户请求额外的数据,比如有效的用户名或者密码证书在尝试之前。
如果不能从一个错误中恢复程序,你应该警告用户。如果你为iOS使用Cocoa Touch,你需要创建安装一个UIAlertView用来显示错误。如果你为OS X使用Cocoa,你需要调用presentError:为任何NSResponder对象(比如一个视图,窗口或者程序对象自己本身),错误会向上传递相应器链条,为了更深层次的配置或者恢复。当她到达应用对象的时候,应用会通过一个警示面板显示错误给用户。
For more information on presenting errors to the user, see Displaying Information From Error Objects.
Generating Your Own Errors
In order to create your own NSError
objects you’ll need to define your own error domain, which should be of the form:
为了创建你自己的NSError对象,你需要定义你自己的错误域,形式如下:
com.companyName.appOrFrameworkName.ErrorDomain |
You’ll also need to pick a unique error code for each error that may occur in your domain, along with a suitable description, which is stored in the user info dictionary for the error, like this:
你需要为每个在你的错误域中可能发生的每个错误挑选一个特别的错误代码,同时需要一个合适的描述,描述存储在用户信息字典里,像这样:
NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain"; |
NSString *desc = NSLocalizedString(@"Unable to…", @""); |
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc }; |
|
NSError *error = [NSError errorWithDomain:domain |
code:-101 |
userInfo:userInfo]; |
This example uses the NSLocalizedString
function to look up a localized version of the error description from a Localizable.strings
file, as described in Localizing String Resources.
If you need to pass back an error by reference as described earlier, your method signature should include a parameter for a pointer to a pointer to an NSError
object. You should also use the return value to indicate success or failure, like this:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr; |
If an error occurs, you should start by checking whether a non-NULL
pointer was provided for the error parameter before you attempt to dereference it to set the error, before returning NO
to indicate failure, like this:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr { |
... |
// error occurred |
if (errorPtr) { |
*errorPtr = [NSError errorWithDomain:... |
code:... |
userInfo:...]; |
} |
return NO; |
} |
Exceptions Are Used for Programmer Errors
OC和其他的程序语言一样也支持异常用法,类似Java或者C++的语法。和NSError一样,异常在Cocoa和Cocoa Touch中都是对象,通过NSException类实例表示。
如果你需要写一个抛出异常的代码,你可以把代码封装到try-catch块中:
@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块捕获以便于你可以处理异常。如果你处理一个底层级的C++库并且使用异常作为错误处理方法,例如,你可以捕获她的异常,然后生成合适的NSError对象,显示给用户。
如果抛出一个异常没被捕获,那么默认的未捕获异常操作会输出一条消息在控制台上,同时终止程序运行。
你不应该使用try-catch块代替标准的OC方法检测。在NSArray的例子中,你应该总是检查一个数组中元素的个数,以便在使用下标访问之前确定好数组的下标。如果你的使用越界,objectAtIndex:方法会抛出异常,这样你就可以提前发现错误——在你将应用展示给用户的时候,你应该避免抛出异常。
For more information on exceptions in Objective-C applications, see Exception Programming Topics.