错误处理
几乎每一款应用都会遇到错误。其中一些错误会超出你的控制,比如用光了磁盘空间或丢失了网络连接。其中一些错误是可以恢复的,如无效的用户输入。并且,虽然所有的开发人员都在努力追求完美,但程序员偶尔也会出现错误。
如果你是来自其他平台和语言的开发者,你过去常常可能使用异常(exceptions)来处理大多数的错误。当你使用 Objective-C 编写代码时,异常(exceptions)仅针对于程序员的错误,如数组的访问越界或无效的方法参数。这些问题是在你应用程序启动之前,在进行测试的时候就应该找到并修复的。
所有其他错误都是NSError
类实例化的表现。本章给出了使用NSError
对象的简要介绍,包括如何使用框架的方法可能会失败并返回错误。需要更多信息,请参见Error Handling Programming Guide
使用NSError处理大多数错误
错误是许多应用程序生命周期中不可避免的一部分。如果你需要从远程Web服务器请求数据,可能会出现各种各样的问题,比如:
- 没有网络连接
- 远程服务器可能无法访问
- 远程服务器可能无法返回你请求的信息
- 你收到的数据可能不符合你的期望
遗憾的是,不可能为每一个可能遇见的问题做应急预案和解决方案。取而代之的是,你必须为错误作出计划并且知道怎样处理它们,并带来最好的用户体验。
一些代理方法会提示你错误
如果你正在实现一个框架类(framework class)的代理对象(delegate object)用于执行某个特定的任务,如从远程Web服务器中下载信息,则通常会发现你至少需要实现一个与错误相关的方法。例如,NSURLConnectionDelegate
协议,包括一个connection:didFailWithError:
方法:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
如果出现一个错误,这个代理方法会被调用并给你提供一个NSError
对象来描述问题。
一个NSError
对象包含一个数字错误代码(numeric error code),域(domain)和描述(description),以及被封装在一个用户信息字典(user info dictionary)中的其他相关信息。
相较于要求每一个可能的错误都有一个独特的数字代码,Cocoa和Cocoa Touch的错误被分为域(domins)。比如,如果发生一个NSURLConnection
的错误,这个connection:didFailWithError:
方法将提供来自NSURLErrorDomain
的一个错误。
这个错误对象还包含一个本地化描述(localized description),如“找不到指定的服务器”。
一些方法通过参数传递错误
一些Cocoa和Cocoa Touch的接口(API)通过参数传回错误。例如,你可能决定使用NSData
的方法writeToURL:options:error:
,将你从Web服务器接收到的数据通过写入磁盘保存起来。这个方法的最后一个参数是对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:
参数传NULL
就行了。
如果可能的话尽量恢复或向用户显示错误
最好的用户体验就是让你的应用程序显而易见地从错误中恢复。如果你正在制作一个远程Web请求,比如,你可能会尝试使用不同的服务器重新发送请求。或者你可能需要在再次请求之前从用户获取更多信息,比如有效的用户名或密码凭据。
如果它不可能从错误中恢复,你应该提醒用户。如果你正在使用Cocoa Touch
为iOS
开发,你需要创建和配置一个UIAlertView
来显示错误。如果你在使用Cocoa
为OS X(mac OS)
开发,你可以在任何NSResponder
对象(如视图、窗口或应用程序对象本身)调用presentError:
并且错误会随响应链(responder chain)
传递以寻求更多配置或恢复。当它到达应用程序对象时,应用程序通过一个警告面板向用户呈现错误。
更多有关向用户展现错误的信息,请参见 Displaying Information From Error Objects。
创建你自己的错误对象
为了创建自己的NSError
对象,你需要定义你自己的错误域(domain),它应该是这样的形式:
com.companyName.appOrFrameworkName.ErrorDomain
你还需要为每一个可能发生在你域(domain)中的错误选择一个唯一的错误代码
,以及一个合适的描述,它被存储在用户信息字典(user info dictionary)
中,像这样:
NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
NSString *desc = NSLocalizedString(@"Unable to…", @"");
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
NSError *error = [NSError errorWithDomain:domain
code:-101
userInfo:userInfo];
这个示例使用NSLocalizedString
函数从一个Localizable.strings
文件来查找错误描述的本地化版本,作为本地化字符串资源(Localizing String Resources)的描述。
如果你需要通过参数传回一个错误作为早期描述,你的方法签名(method signature)应该包含一个指向NSError
对象的指针作为参数。你也应该使用返回值来表示成功或者失败,如:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr;
如果出现错误,在你尝试引用它来设置错误之前,你首先应该检查是否给错误指针提供了一个非空指针,在返回NO
来表示失败之前,像这样:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr {
...
// error occurred
if (errorPtr) {
*errorPtr = [NSError errorWithDomain:...
code:...
userInfo:...];
}
return NO;
}
异常用来处理程序员的错误
Objective-C
支持异常(exceptions),有和其他许多编程语言有相同的用法,和Java或C++有相似的语法。和NSError
一样,在Cocoa
和Cocoa Touch
中的异常都是对象,通过NSException
类的实例来表示。
如果你需要编写可能引发异常的代码,你可以将该代码封装在一个try-catch
代码块(block)中:
@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
代码块捕获,你便可以处理它。比如,如果你在使用用异常(exceptions)来处理错误的底层C++库,你可能会捕获到异常并创建合适的NSError
对象来向用户显示。
如果一个异常被抛出但没有被捕获,默认的没有捕获异常处理会记录信息到控制台并终止程序。
你不应该在标准程序检查Objective-C方法的地方使用try-catch
代码块。以一个NSArray
为例,你应该在试图访问指定下标的对象之前,总是检查数组的count
来确定元素的数量。如果你做了一个越界的访问请求,objectAtIndex:
方法会抛出一个异常,在开发周期的早期你就可以找到在你代码中的漏洞(bug),你在给用户的应用中应该避免抛出异常。
想了解更多关于在Objective-C程序中异常的信息,请参考Exception Programming Topics。