几乎每个应用程序都会遇到错误。其中一些错误将超出您的控制范围,例如耗尽磁盘空间或丢失网络连接。其中一些错误将是可恢复的,例如无效的用户输入。而且,尽管所有开发人员都力求完美,但偶尔也会出现程序员错误。
如果您来自其他平台和语言,那么您可能习惯于处理大多数错误处理的异常。当您使用Objective-C编写代码时,异常仅用于程序员的错误,如边界数组访问或无效的方法参数。这些问题是您在发布应用程序之前应该发现并修复的问题。
所有其他错误都由NSError类的实例表示。本章简要介绍了使用NSError对象,包括如何使用可能失败和返回错误的框架方法。有关进一步的信息,请参见错误处理编程指南 Error Handling Programming Guide。
对大多数错误使用NSError
错误是任何应用程序生命周期中不可避免的一部分。例如,如果您需要从远程web服务请求数据,可能会出现各种各样的潜在问题,包括:
没有网络连接
远程web服务可能无法访问
远程web服务可能无法提供您请求的信息
你收到的数据可能与你预期的不符
遗憾的是,我们不可能为每个可能出现的问题建立应急计划和解决方案。相反,您必须计划错误并知道如何处理它们以提供最佳的用户体验。
一些委托方法会提醒您错误
如果您正在实现一个用于执行特定任务的框架类的委托对象,比如从远程web服务下载信息,您通常会发现您需要实现至少一个错误相关的方法。例如,NSURLConnectionDelegate协议包含一个connection:didFailWithError:方法:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
如果出现错误,将调用此委托方法为您提供一个NSError对象来描述问题。
NSError对象包含一个数字错误代码、域和描述,以及在用户信息字典中打包的其他相关信息。
而不是要求每个可能的错误都有唯一的数字代码,Cocoa和Cocoa Touchu错误被划分为域。如果在NSURLConnection中发生错误,例如,connection:didFailWithError:上面的方法将提供来自NSURLErrorDomain的错误。
错误对象还包括一个本地化描述,例如“无法找到具有指定主机名的服务器(A server with the specified hostname could not be found.)”。
有些方法通过引用来传递错误
一些Cocoa和Cocoa Touch API通过引用来传递错误。例如,您可能决定将从web服务接收到的数据写入磁盘,使用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指针以指向描述该问题的错误对象。
当处理通过引用传递的错误时,重要的是测试方法的返回值,以查看是否发生了错误,如上所示。不要只是测试看错误指针是否被设置为指向错误。
提示:如果您对错误对象不感兴趣,请为错误参数传递NULL.
如果可能,请恢复,或向用户显示错误
最好的用户体验是让你的应用程序从错误中透明地恢复。例如,如果您正在创建一个远程web请求,您可以尝试使用不同的服务器再次发出请求。或者,您可能需要在再次尝试之前向用户请求其他信息,比如有效的用户名或密码凭证。
如果无法从错误中恢复,应该提醒用户。如果您正在为iOS开发Cocoa Touch,那么您需要创建和配置一个UIAlertView来显示错误。如果您正在为OS X开发Cocoa,您可以调用presentError:在任何NSResponder对象(比如视图、窗口,甚至是应用程序对象本身)上,错误将会传播到responder chain以进行进一步的配置或恢复。当它到达应用程序对象时,应用程序通过一个警告面板向用户呈现错误。
有关向用户显示错误的更多信息,请参见从错误对象中显示信息Displaying Information From Error Objects。
创造你自己的错误
为了创建自己的NSError对象,您需要定义自己的错误域,该域应该是:
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];
本例使用NSLocalizedString函数从可本地化的地方查找错误描述的本地化版本。字符串文件,如本地化字符串资源所述。
如果您需要像前面描述的那样通过引用传递一个错误,那么您的方法签名应该包含一个指向指向NSError对象指针的参数。您还应该使用返回值来表示成功或失败,如下所示:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr;
如果发生错误,您应该首先检查是否为错误参数提供了一个非空指针,然后尝试取消它来设置错误,然后返回NO表示失败,如下所示:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr {
...
// error occurred
if (errorPtr) {
*errorPtr = [NSError errorWithDomain:...
code:...
userInfo:...];
}
return NO;
}
异常用于程序员错误
Objective-C和其他编程语言一样支持异常,类似于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块来代替Objective-C方法的标准编程检查。例如,在NSArray的情况下,在尝试访问给定索引的对象之前,应该始终检查数组的计数来确定项目的数量。objectAtIndex:方法会抛出一个异常,如果您做出了一个超出范围的请求,这样您就可以在开发周期的早期发现代码中的错误——您应该避免在您发送给用户的应用程序中抛出异常。
有关Objective-C应用程序中异常的更多信息,请参见异常编程主题 Exception Programming Topics。