处理错误 – Objective-C编程快速入门教程

上一章Objective-C编程快速入门教程请查看:使用代码块block?
处理错误几乎每个应用程序都会遇到错误。其中一些错误将超出你的控制范围,例如耗尽磁盘空间或丢失网络连接。其中一些错误是可恢复的,比如无效的用户输入。而且,虽然所有开发人员都在追求完美,但偶尔也会出现程序员错误。
如果你来自其他平台和语言,那么你可能习惯于使用异常来处理大多数错误。当你用Objective-C写代码时,异常只用于程序员的错误,比如越界的数组访问或者无效的方法参数。在发布应用程序之前,你应该在测试期间发现并修复这些问题。
所有其他错误都由NSError类的实例表示。本章简要介绍了如何使用NSError对象,包括如何使用可能失败和返回错误的框架方法。有关更多信息,请参见错误处理编程指南。
对大多数错误使用NSError错误是任何应用程序生命周期中不可避免的一部分。例如,如果你需要从远程web服务请求数据,可能会出现各种各样的潜在问题,包括:

  • 没有网络连接
  • 远程web服务可能无法访问
  • 远程web服务可能无法提供你请求的信息
  • 你收到的数据可能与你期望的不一致
遗憾的是,不可能为每一个可以想到的问题建立应急计划和解决方案。相反,你必须为错误做好计划,并知道如何处理它们,以提供最佳的用户体验。
一些委托方法会提醒你出错
如果你正在实现一个委托对象,以便与执行特定任务(如从远程web服务下载信息)的框架类一起使用,那么你通常会发现需要实现至少一个与错误相关的方法。例如,NSURLConnectionDelegate协议包含一个连接:didFailWithError:方法:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

如果发生错误,将调用此委托方法为你提供一个NSError对象来描述问题。
NSError对象包含数字错误代码、域和描述,以及打包在用户信息字典中的其他相关信息。
不是要求每个可能的错误都有惟一的数字代码,而是将Cocoa和Cocoa Touch错误划分为域。例如,如果在NSURLConnection中发生错误,上面的connection:didFailWithError:方法将提供来自NSURLErrorDomain的错误。
错误对象还包括本地化描述,例如“找不到具有指定主机名的服务器”。
有些方法通过引用传递错误
一些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作为error:参数。
如果可能,请恢复或将错误显示给用户
最好的用户体验是从错误中透明地恢复。例如,如果你正在发出远程web请求,你可以尝试使用不同的服务器再次发出请求。或者,在再次尝试之前,你可能需要从用户请求其他信息,例如有效的用户名或密码凭据。
如果无法从错误中恢复,则应向用户发出警告。如果你正在为iOS开发Cocoa Touch,你需要创建并配置一个UIAlertView来显示错误。如果你正在为OS X使用Cocoa开发,你可以在任何NSResponder对象上调用presentError:(比如一个视图,窗口,甚至是应用程序对象本身),错误会传播到responder链上以进行进一步的配置或恢复。当它到达应用程序对象时,应用程序通过警告面板将错误显示给用户。
有关向用户显示错误的更多信息,请参见显示来自错误对象的信息。
生成自己的错误
为了创建你自己的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函数从Localizable查找错误描述的本地化版本。字符串文件,如本地化字符串资源中所述。
如果你需要像前面描述的那样通过引用传递回一个错误,那么你的方法签名应该包括一个指向NSError对象指针的指针的参数。你还应该使用返回值来指示成功或失败,如下所示:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr;

如果发生错误,你应该首先检查是否为error参数提供了一个非空指针,然后再尝试取消引用以设置错误,最后返回NO表示失败,如下所示:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr { ... // error occurred if (errorPtr) { *errorPtr = [NSError errorWithDomain:... code:... userInfo:...]; } return NO; }

异常用于程序员错误【处理错误 – Objective-C编程快速入门教程】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,在尝试访问给定索引处的对象之前,应该始终检查数组的计数以确定项的数量。如果你发出了越界请求,以便在开发周期的早期发现代码中的bug,那么objectAtIndex:方法将抛出异常—你应该避免在交付给用户的应用程序中抛出异常。
有关Objective-C应用程序中的异常的更多信息,请参阅异常编程主题。

    推荐阅读