Error Handling Programming Guide
每个程序都必须处理在运行时发生的错误. 例如, 该程序可能无法打开文件, 或者可能无法解析XML文档. 通常此类错误需要程序通知用户有关这些错误的信息. 也许程序可以尝试解决导致错误的问题
Cocoa(和Cocoa Touch)为开发人员提供了用于这些任务的编程工具. Foundation中的NSError类以及Application Kit中的新方法和机制来支持应用程序中的错误处理. NSError对象封装了特定于错误的信息, 包括引发错误的域(子系统)和要在错误警报中显示的本地化字符串. 对于应用程序, 还有一种体系结构, 允许应用程序中的各种对象完善错误对象中的信息, 并可能从错误中恢复.
重要 NSError类在OS X和iOS上均可用. 但是错误响应程序和错误恢复API和机制仅在应用程序套件(OS X)中可用
Error Objects, Domains, and Codes
Cocoa程序使用NSError对象传达有关运行时错误的信息, 而且这些信息需要告知用户. 在大多数情况下程序会在dialog或sheet中显示此错误信息. 但它也可能解释信息并要求用户尝试从错误中恢复, 或者尝试自行更正错误
NSError对象(或者简单地说是错误对象)的核心属性是domain错误域、特定于域的错误代码和包含与错误相关的对象的"user info"字典,其中包含最重要的描述和恢复字符串
本章解释错误对象的原因, 描述其属性, 并讨论如何在Cocoa代码中使用它们
Why Have Error Objects?
由于是对象, NSError实例对象相较于简单的错误码, 错误字符串还是有不少优点的. 它们同时封装多个错误信息, 包括各种本地化的错误字符串. NSError对象也可以存档和复制, 并且可以在应用程序中传递和修改. 尽管NSError不是抽象类(因此可以直接使用), 但也可以通过子类扩展NSError类
由于分层错误域的概念, NSError对象可以嵌入来自底层子系统的错误, 从而提供有关错误的更详细和细微差别的信息. 错误对象还通过保留对被指定为错误的恢复尝试者的对象的引用来提供错误恢复的机制
Error Domains
由于历史原因, OS X中的错误代码被隔离到域中. 例如, 类型为OSStatus的Carbon错误码起源于OS X之前的Macintosh操作系统版本. 另一方面, POSIX错误码源自各种符合POSIX的UNIX"风格",例如BSD. Foundation框架在NSError.h中为四个主要错误域声明以下字符串常量
- NSMachErrorDomain
- NSPOSIXErrorDomain
- NSOSStatusErrorDomain
- NSCocoaErrorDomain
上面的域常数序列表示域的一般分层其中Mach错误域位于最底层, 可以通过向NSError对象发送域消息来获取错误域
除了四个主要领域外, 还有一些错误域是特定于框架的, 甚至特定于类组或单个类. 例如Web Kit框架在其Objective-C实现中有自己的域来解决错误, 那就是WebKitErrorDomain. 在Foundation框架内, URL类(NSURLErrorDomain)和XML类(NSXMLParserErrorDomain)一样, 都有自己的错误域. NSStream类本身定义了两个错误域, 一个用于SSL错误, 另一个用于SOCKS错误
Cocoa错误域(NSCocoaErrorDomain)包含Cocoa框架的所有错误码-当然这些框架的特定于类的域中的错误码除外. 这些框架不仅包括Foundation, UIKit和Application Kit, 还包括Core Data和可能的其他Objective-C框架
域有几个有用的目的. 它们为Cocoa程序提供了一种方法来识别OS X子系统检测的错误. 它们还有助于防止来自不同子系统的具有相同数值的错误代码之间的冲突. 另外, 域允许基于子系统的分层在错误代码之间建立因果关系, 例如NSOSStatusErrorDomain中的错误可能在存在底层的NSMachErrorDomain错误
可以创建自己的错误域和错误代码, 以用于自己的框架, 甚至自己的应用程序. 建议域的字符串常量的形式为com.company.framework_or_app.ErrorDomain
Error Codes
错误码标识特定域中的特定错误, 它是一个有符号整数, 指定为程序符号的值. 通过向NSError对象发送code消息来获取错误代码. 错误码在每个主要域的一个或多个头文件中声明和记录
主域中错误码的头文件
| Domain | Header file |
|---|---|
| Mach | /usr/include/mach/kern_return.h |
| POSIX | /usr/include/sys/errno.h |
| Carbon (OSStatus) | /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/MacErrors.h |
| Cocoa | 见下表 |
当前在Cocoa域中声明错误码的框架和头文件
| Framework/Header | Description |
|---|---|
| <Foundation/FoundationErrors.h> | Generic Foundation error codes |
| <AppKit/AppKitErrors.h> | Generic Application Kit error codes |
| <CoreData/CoreDataErrors.h> | Core Data error codes |
为了说明如何测试错误并采取措施, 假设要在写入文件的操作过程中测试潜在的POSIX错误, 如果查阅了/usr/include/sys/errno.h中声明的POSIX错误码, 将会看到如下列表
POSIX错误代码声明的一部分(errno.h)
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* Input/output error */
#define ENXIO 6 /* Device not configured */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file descriptor */
#define ECHILD 10 /* No child processes */
#define EDEADLK 11 /* Resource deadlock avoided */
/* 11 was EAGAIN */
#define ENOMEM 12 /* Cannot allocate memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address *#H
可以选择要测试的错误条件, 并在下面的示例代码中使用它们
// underError is underlying-error object of a Cocoa-domain error
if ([[underError domain] isEqualToString:NSPOSIXErrorDomain]) {
switch([underError code]) {
case EIO:
{
// handle POSIX I/O error
}
case EACCES:
{
// handle POSIX permissions error
{
// etc.
}
}
可以声明自己的错误代码以供您自己的应用程序或框架使用, 但错误代码应属于您自己的域. 永远不要将错误代码添加到您不属于"所有者"的现有域中
The User Info Dictionary
每个NSError对象都有一个"user info"字典来保存域和错误码以外的错误信息. 可以通过将userInfo消息发送到NSError对象来访问此词典. 与其他类型的容器对象相比, NSDictionary对象的优点在于它很灵活. 它可以携带有关错误的自定义信息. 所有user info词典都包含(或可以包含)多个与错误相关的预定义字符串和对象值
Localized Error Information
NSError对象的重要角色是包含错误信息, 程序可以在dialog或sheet中显示这些错误信息. 此信息通常以字符串的形式存储在用户信息字典中, 分为几个类别:说明, 故障原因, 恢复建议和恢复选项. 创建NSError对象时, 应将本地化的字符串插入字典中, 除非要懒加载它们
注意 不要期望每个错误对象的user info字典都包含本地化的字符串, 例如NSError的子类可以重写localizedDescription以从错误域, 错误码和上下文动态组成这些字符串, 而不是存储它们
通常可以通过以下两种方式之一访问与NSError对象关联的本地化信息, 对user info字典使用objectForKey:方法传入一个指定的参数获取. 或者可以向NSError对象发送等效消息. 应该发送消息而不是使用字典键来访问本地化的字符串. 错误对象可能不会将字符串存储在字典中, 而是选择动态地将其组成. 字典被设计为一种后备机制, 而不是错误字符串的唯一存储库. 而是使用字典键将自己的字符串存储在user info字典中
以下摘要包括字典键和用于访问本地化字符串的方法
- 错误说明/描述
- 错误的主要描述, 以较大的粗体显示. 它通常包括失败原因. 如果
user info字典中没有错误描述,则NSError将从错误域和代码中构造一个. 尝试从该域内的函数或方法中获取合适的字符串 - User info key:
NSLocalizedDescriptionKey - Method:
localizedDescription (never returns nil)
- 错误的主要描述, 以较大的粗体显示. 它通常包括失败原因. 如果
- 失败原因
- 简短的句子说明发生错误的原因. 它通常是错误描述的一部分. 诸如
presentError:之类的方法不会自动显示失败原因, 因为它已包含在错误描述中. 失败原因是针对只希望显示失败原因的客户端 - User info key:
NSLocalizedFailureReasonErrorKey - Method:
localizedFailureReason (can return nil)
- 简短的句子说明发生错误的原因. 它通常是错误描述的一部分. 诸如
注意 一个例子可以帮助阐明错误描述和失败原因之间的关系, 如:错误对象的错误描述为"由于磁盘已满无法保存文件", 伴随的失败原因是"磁盘已满"
- 恢复建议
- 辅助语句理想地告诉用户他们可以采取哪些措施来从错误中恢复. 出现在错误说明下方, 字体较浅. 如果使用
alert和button来显示恢复建议, 它应该使用与恢复选项指定的相同的标题(NSLocalizedRecoveryOptionsErrorKey). 可以将此字符串用作纯提示性消息, 以补充错误说明和失败原因 - User info key:
NSLocalizedRecoverySuggestionErrorKey - Method:
localizedRecoverySuggestion (never returns nil)
- 辅助语句理想地告诉用户他们可以采取哪些措施来从错误中恢复. 出现在错误说明下方, 字体较浅. 如果使用
- 恢复选项
alert和button的标题(作为字符串)数组, 一般情况下,alert sheets和dialogs, 对于错误消息仅具有"OK"按钮以消除alert弹窗. 数组中的第一个字符串是最右边按钮的标题, 下一个字符串是第一个按钮左侧的按钮的标题, 依此类推. 请注意, 如果为错误对象指定了恢复尝试程序, 则recovery-options数组应包含多个字符串. 恢复尝试程序将访问恢复选项以解释用户的选择- User info key: NSLocalizedRecoveryOptionsErrorKey
- Method:
localizedRecoveryOptions (if returns nil, implies a single “OK button)
NSError对象的本地化字符串
注意 从OS X版本10.4开始, 可以使用NSAlert的alertWithError:class方法来方便地创建显示alert dialogs 或者 sheets. 该方法从传入的NSError对象中提取其消息文本, 信息性文本和按钮标题的本地化信息. 还可以使用presentError:消息显示error alerts
The Recovery Attempter
NSError对象的user info字典也可以包含恢复尝试器. 恢复尝试程序是实现NSErrorRecoveryAttempting非正式协议的一个或多个方法的对象. 在许多情况下,这是创建NSError对象的同一对象,但是它可以是任何其他可能知道如何从特定错误中恢复的对象.
如果已为NSError对象指定了恢复尝试程序, 并且还指定了多个恢复选项, 则在显示error alert并且用户选择恢复选项时, 将为恢复尝试程序提供从错误中恢复的机会. 通过将recoveryAttempter发送到NSError对象来访问恢复尝试程序. 可以使用键NSRecoveryAttempterErrorKey将恢复尝试程序添加到user info字典中
Underlying Error
user info词典有时可以包含另一个NSError对象, 该对象包含了子系统中的错误, 可以查询此底层错误对象, 以获取有关错误原因的更多特定信息
可以使用NSUnderlyingErrorKey字典键访问底层错误对象
Domain-Specific Keys
各种错误域中的许多域都指定用于从user info字典访问特定信息项的键. 此信息补充了错误对象中的其他信息. 例如Cocoa域定义了键NSStringEncodingErrorKey,NSURLErrorKey和NSFilePathErrorKey
检查头文件或错误域的文档以了解它们声明了哪些特定于域的键
Using and Creating Error Objects
以下各节介绍如何处理从框架方法返回的NSError对象、如何使用错误对象显示错误消息、如何创建错误对象以及如何实现通过引用返回错误对象的方法
Handling Error Objects Returned From Methods
Cocoa和 Cocoa Touch类的许多方法都包括作为最后一个参数对NSError对象的直接或间接引用. 在某些基础和UIKit方法中, 将 NSError对象视为委派方法的参数. 以下声明来自 UIKit 框架的 UIWebViewDelegate 协议, 委托将实现这样的方法以找出操作是否失败
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
调用的 Cocoa框架的某些方法包括对 NSError对象的间接引用. 这些方法通常执行一些操作例如创建文档、编写文件或加载 URL. 例如以下方法声明来自 NSDocument 类头文件
- (BOOL)writeToURL:(NSURL *)absoluteURL
ofType:(NSString *)typeName
error:(NSError **)outError;
如果诸如此类的方法在其实现中遇到错误, 则它直接返回NO表示失败, 并在最后一个参数中间接返回(如果客户端代码要求一个NSError对象来描述错误
如果要评估错误, 请在调用诸如writeToURL:ofType:error之类的方法之前声明一个NSError对象变量. 调用该方法时, 请传递一个指向该变量的指针(如果您对该错误不感兴趣则只需传递NULL). 如果该方法直接返回nil或NO则检查NSError对象以确定错误原因或仅显示错误alert
示例
NSError *theError;
BOOL success = [myDoc writeToURL:[self docURL] ofType:@"html" error:&theError];
if (success == NO) {
// Maybe try to determine cause of error and recover first.
NSAlert *theAlert = [NSAlert alertWithError:theError];
[theAlert runModal]; // Ignore return value.
}
Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.
上述中的代码使用NSError返回的结果向用户立即显示错误alert, Cocoa域中的错误对象总是被本地化并准备呈现给用户, 因此通常无需进一步评估即可呈现它们.
不仅可以显示基于NSError对象的错误消息, 还可以检查对象以确定是否可以做其他事情. 例如可以以略有不同的方式再次执行该操作从而避免该错误. 但是如果执行此操作, 则应首先请求用户的许可以执行修改后的操作.
评估NSError对象时, 请始终使用对象的域和错误码作为测试的基础, 而不要使用描述错误或如何从错误中恢复的字符串, 字符串通常位于本地因此可能会有所不同. 除少数例外(例如NSURLErrorDomain域)外从Cocoa框架方法返回的预先存在的错误始终在NSCocoaErrorDomain域中. 因为有例外, 可能需要测试顶级错误是否属于该域. 从Cocoa方法返回的错误对象通常可以包含表示下级子系统(例如BSD层)返回的底层错误对象(NSPOSIXErrorDomain)
当然要成功评估错误, 必须预料到方法调用可能返回的错误. 而且应确保编写的代码充分处理了将来可能会返回的新错误
重要 应该始终对NSUserCancelledError错误码(在NSCocoaErrorDomain中)进行特殊情况测试. 此错误码指示用户取消了某操作(例如通过按 Command-period). 在这种情况下,不应显示任何错误对话框
Error-Handling Alternatives in OS X
如果正在开发Mac应用程序, 则在收到NSError对象后还可以执行许多其他操作
- 如果知道如何从错误中恢复但是需要用户的批准, 则可以创建错误对象的新版本在其中添加恢复尝试器
- 将错误对象发送到错误响应者链, 以便应用程序中的其他对象可以添加到该对象或尝试从错误中恢复
- 在执行此操作之前, 可能可以从当前编程上下文中补充错误中的信息, 然后创建一个包含此丰富信息的新错误对象
- 如果使用返回的
NSError对象作为新错误对象的基础(过添加恢复尝试程序或补充信息)则可以- 立即显示消息
- 将错误传递给下一个错误响应者
Displaying Information From Error Objects
有几种不同的方法可以在NSError对象中显示信息. 您可以从错误对象中提取本地化的描述(或失败原因(, 恢复建议和恢复选项,并使用它们来初始化NSAlert,UIAlertView或UIActionSheet对象(或OS X模态文档表)的图块和消息文本. 这种通用方法使您可以对错误alert的内容和表示进行较大程度的控制
显示主要由错误对象属性组成的alert
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSString *titleString = @"Error Loading Page";
NSString *messageString = [error localizedDescription];
NSString *moreString = [error localizedFailureReason] ?
[error localizedFailureReason] :
NSLocalizedString(@"Try typing the URL again.", nil);
messageString = [NSString stringWithFormat:@"%@. %@", messageString, moreString];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:titleString
message:messageString delegate:self
cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
[alertView show];
}
但是不必使用框架提供的本地化错误字符串. 例如如果认为它们的描述性不够, 或者想用上下文相关的信息来补充它们, 则可以通过错误的域和错误码来识别错误, 然后替换成自己的字符串值. 采用前面示例中使用的委托方法传递给webView:didFailLoadWithError:中的委托的错误对象几乎总是属于NSURLErrorDomain域, 可以找出该域的哪个代码与错误相关联, 并用自己的字符串替换错误对象中包含的字符串(NSURLErrorDomain及其代码在NSURLError.h中声明)
示例 根据错误域和代码分配自定义消息字符串
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSString *errorMsg;
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
switch ([error code]) {
case NSURLErrorCannotFindHost:
errorMsg = NSLocalizedString(@"Cannot find specified host. Retype URL.", nil);
break;
case NSURLErrorCannotConnectToHost:
errorMsg = NSLocalizedString(@"Cannot connect to specified host. Server may be down.", nil);
break;
case NSURLErrorNotConnectedToInternet:
errorMsg = NSLocalizedString(@"Cannot connect to the internet. Service may not be available.", nil);
break;
default:
errorMsg = [error localizedDescription];
break;
}
} else {
errorMsg = [error localizedDescription];
}
UIAlertView *av = [[UIAlertView alloc] initWithTitle:
NSLocalizedString(@"Error Loading Page", nil)
message:errorMsg delegate:self
cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
[av show];
}
Propagating Errors for Display by the Application Object (OS X)
Application Kit提供了快捷显示错误alert的方式. presentError:和presentError:modalForWindow:delegate:didPresentSelector:contextInfo:方法允许发出错误alert, 该alert最终由全局应用程序对象显示, 前一个方法请求application-modal alert弹窗方式, 后一个则是document-modal alert模式的弹窗. 必须将这些当前错误消息之一发送到错误响应程序链中的对象: 视图对象, 窗口对象, NSDocument对象, NSWindowController对象, NSDocumentController对象, NSApp(如果将消息发送到视图, 则理想情况下它应该是与发生错误的条件相关联的视图对象)
显示document-modal error alert
NSError *theError;
NSData *theData = [doc dataOfType:@”xml” error:&theError];
if (!theData && theError)
[anyView presentError:theError
modalForWindow:[doc windowForSheet]
delegate:self
didPresentSelector:
@selector(didPresentErrorWithRecovery:contextInfo:)
contextInfo:nil];
用户取消显示弹窗后NSApp会调用一个由代理实现的方法(didPresentSelector:), 此方法中的代理检查恢复尝试对象(如果有)是否从错误中恢复并做出相应响应
代理方法
- (void)didPresentErrorWithRecovery:(BOOL)recover contextInfo:(void *)info {
if (recover == NO) { // Recovery did not succeed, or no recovery attempted.
// Proceed accordingly.
}
}
直接显示错误alert
NSAlert *theAlert = [NSAlert alertWithError:theError];
NSInteger button = [theAlert runModal];
if (button != NSAlertFirstButtonReturn) {
// handle
}
注意 方法presentError:和presentError:modalForWindow:delegate:didPresentSelector:contextInfo:默认忽略NSCocoaErrorDomain域中的NSUserCancelledError错误
Creating and Returning NSError Objects
可以声明并实现自己的间接返回NSError对象的方法. 可以使用NSError参数作为候选方法的方法包括打开和读取文件, 加载资源, 解析格式文本等. 通常这些方法不应通过存在NSError对象来指示错误. 相反, 他们应该从方法中返回NO或nil来指示发生了错误. 返回NSError对象以描述错误
如果要在此类方法的实现中通过引用返回NSError对象则必须创建NSError对象. 可以通过分配NSError对象内存, 然后使用NSError的initWithDomain:code:userInfo:方法对其进行初始化, 或者使用类工厂方法errorWithDomain:code:userInfo:来创建错误对象. 正如这两种方法的关键字所指示的那样, 必须为初始化程序提供一个域(字符串常量), 一个错误码(一个带符号的整数)以及一个包含描述性和支持性信息的user info字典. 应该确保用user info中的所有字符串都已本地化
重要 仅当方法中有错误并且方法直接返回NO时,才应修改NSError参数. 在为对象分配参数之前, 请始终进行测试以查看该参数是否为non-NULL并且永远不要将nil分配给错误参数
实现一个返回NSError对象的方法
- (NSString *)fooFromPath:(NSString *)path error:(NSError **)anError {
const char *fileRep = [path fileSystemRepresentation];
int fd = open(fileRep, O_RDWR|O_NONBLOCK, 0);
if (fd == -1) {
if (anError != NULL) {
NSString *description;
NSDictionary *uDict;
int errCode;
if (errno == ENOENT) {
description = NSLocalizedString(@"No file or directory at requested location", @"");
errCode = MyCustomNoFileError;
} else if (errno == EIO) {
// Continue for each possible POSIX error...
}
// Create the underlying error.
NSError *underlyingError = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain
code:errno userInfo:nil];
// Create and return the custom domain error.
NSDictionary *errorDictionary = @{ NSLocalizedDescriptionKey : description,
NSUnderlyingErrorKey : underlyingError, NSFilePathErrorKey : path };
*anError = [[NSError alloc] initWithDomain:MyCustomErrorDomain
code:errCode userInfo:errorDictionary];
}
return nil;
}
// ...
在此示例中返回的错误对象在其user info字典中包括导致错误的路径. 可以使用源自底层子系统的错误作为返回给调用方的错误对象的基础
可以使用代码以相同方式处理的抛出的异常. NSException对象与NSError对象兼容, 因为它的属性是名称,原因和user info字典, 可以轻松地将异常对象中的信息传输到错误对象.
A Note on Errors and Exceptions
重要的是要记住Cocoa和Cocoa Touch中错误对象和异常对象之间的区别, 以及何时在代码中使用一个或另一个. 它们具有不同的目的不应混淆
异常(由NSException对象表示)是用于编程错误的, 例如超出范围的数组索引或无效的方法参数. 用户级错误(由NSError对象表示)是针对运行时错误的, 例如找不到文件或无法读取特定编码的字符串时. 抛出异常的条件是由于编程错误引起的, 应该在发布应用之前处理这些错误. 运行时错误总是会发生的, 应该(通过NSError对象)将这些错误传达给用户, 并提供所需的详细信息
尽管理想情况下应在部署之前对异常进行处理, 但是由于某些真正异常的情况(例如"内存不足"或"启动卷不可用")出厂的应用程序仍会遇到异常. 最好允许最高级别的应用程序(全局应用程序对象本身)处理这些情况
Error Responders and Error Recovery
NSError对象为Cocoa编程带来了相当大的优势. Cocoa框架还使NSError对象在错误表示和错误恢复的体系结构中扮演着重要角色. 这些体系结构增强了错误对象的实用性. 它们使Cocoa应用程序可以向用户提供更丰富, 可自定义的消息范围. 并尝试从错误中恢复并通知用户
注意 本章中描述的错误表示和错误恢复体系结构仅在Mac上可用
The Error-Responder Chain
Application Kit主要通过NSResponder类定义了一种机制:称为响应者链. 通过该机制, 应用程序中的事件和操作消息沿视图层次结构向上传递到Windows, 最终传递到应用程序对象. 该应用程序套件定义了类似的对象链, 用于错误处理和表示
要启动NSError对象在错误响应者链上的旅程, 可以向链中的任何对象发送以下两条消息之一:
presentError:- 用于显示在应用程序模式中的错误消息 --
modal alert dialogs
- 用于显示在应用程序模式中的错误消息 --
presentError:modalForWindow:delegate:didPresentSelector:contextInfo:- 用于文档中显示的错误消息 --
modal alert sheets
- 用于文档中显示的错误消息 --
尽管这些方法由NSResponder类声明, 但是也可以将它们发送到NSDocument和NSDocumentController类的对象(当然NSResponder类是NSView,NSWindow, NSApplication和NSWindowController类的超类)
presentError:...方法的默认行为(NSApplication实现除外)是在将presentError:..消息转发到链中的下一个对象之前, 将willPresentError:发送给self. 子类可以实现willPresentError:方法, 以检查传入的NSError对象并返回自定义对象. 如果子类比父类更了解引起错误的条件或者最了解如何从错误中恢复, 则子类可能这样做. 出于说明目的假设视图层次结构下方的视图对象收到presentError:消息. 如下图所示, 它将willPresentError:发送给self, 然后将presentError:发送给其父视图, 并向其传递任何已修改的NSError对象, 原始视图的父视图执行相同的操作, 直到最终窗口的内容视图将presentError:消息发送到其窗口对象
错误响应者链—第一部分
presentError:消息以这种方式在错误响应者链中继续前进直到到达全局应用程序NSApp对象为止. 下图所示, NSApp将application:willPresentError:消息发送给它的代理, 使它具有与链中子类对象相同的机会来检查错误对象并可能对其进行修改, 但无需自定义子类. 当代理返回, NSApp则会显示如下的弹窗
错误响应者链—第二部分
重要 presentError:modalForWindow:delegate:didPresentSelector:contextInfo:和presentError:并不推荐重写
错误响应程序链中对象的确切顺序根据应用程序类型而有所不同. 对于基于文档的应用程序, 错误响应者链包括文档对象, 窗口控制器和文档控制器以及视图, 窗口和NSApp
基于文档的应用程序的错误响应者链
一些Cocoa应用程序不是基于文档的, 但仍使用一个或多个窗口控制器
具有窗口控制器的非文档应用程序的错误响应程序链
还有一种, 简单的Cocoa应用程序(不基于文档的应用程序和不使用窗口控制器的应用程序)具有错误响应程序序列
简单(非文档)应用程序的错误响应者链
Error Customization
如上一节所述, 在错误响应者链中, 如果链中对象的自定义子类实现了willPresentError:方法, 则它们有机会检查和自定义NSError对象. 在链的末尾应用程序代理在application:willPresentError:中具有相同的机会. 那么这些方法可以进行什么样的测试和自定义?
在这两种方法中首先要确定错误是什么. 这样做时, 请根据可能与错误状况相关的常量测试NSError对象的域和错误码. 不要评估描述或恢复字符串, 因为它们可能会有所不同, 尤其是在本地化的情况下. 还可以通过使用特定于域的键从user info字典中提取各种信息来缩小错误原因的范围
重要 应该始终对NSUserCancelledError错误代码进行特殊情况测试(在NSCocoaErrorDomain中). 此代码指示用户取消了该操作(例如,通过按Command-period), 在这种场景下不应该显示alert弹窗/对话框
可能还想找出错误对象是否具有基础错误, 可以使用NSUnderlyingErrorKey键从user info字典中访问此对象. 如果存在底层错误, 并且此对象的user info字典中有失败原因则可以将此本地化字符串附加到错误描述中, 以创建内容更丰富的描述.
如果您决定知道如何从错误中恢复, 则可以将一个对象作为恢复尝试者添加到user info字典中. 为了使恢复尝试有效,它必须满足Error Recovery(下面一节)中概述的要求
如果要自定义收到的NSError对象设置定义错误域和错误码, 则可以选择将原始错误作为底层错误存储user info字典中. 为此, 请使用键NSUnderlyingErrorKey(或重写recoveryAttempter方法)
不能修改收到的NSError对象, 因为该类没有提供setter方法, 并且用户信息字典是不可变的. 自定义错误时必须创建一个新的NSError对象, 并使用新数据以及继承的旧的错误对象中的数据进行初始化
Error Recovery
恢复尝试程序是指定为根据用户请求尝试从特定错误中恢复的对象. 例如, 假设某个程序由于被锁定而无法保存文件.
恢复尝试程序可以尝试先对其进行解锁然后再覆盖它. 错误恢复机制与委托设计模式相似,因为要求指定的对象(恢复尝试程序)响应用户操作. NSError对象可以封装恢复尝试程序和恢复选项,这些内容是要在错误alert中显示的按钮标题数组. 在按钮标题中有一个请求错误恢复的按钮. 当显示错误警报并且用户单击按钮时, 应用程序会向恢复尝试程序发送一条消息并向其传递所单击按钮的索引. 如果单击了"恢复”"按钮, 则恢复尝试程序将尝试以避免错误或解决引起错误的条件的方式来完成操作. 最后, 恢复尝试程序将通知应用程序对象或文档模式工作表代理告知是否成功
作为用户的选择, 发生错误恢复有三个要求
- 恢复尝试对象必须实现
NSErrorRecoveryAttempting非正式协议方法之一attemptRecoveryFromError:optionIndex:delegate:didRecoverSelector:contextInfo:或者attemptRecoveryFromError:optionIndex:, 这取决于错误alert模式别是document-modall (sheet)还是application-modal (dialog) recoveryAttempter方法必须返回合适的对象. 为此可以将恢复尝试程序作为NSRecoveryAttempterErrorKey的值添加到user info字典中,也可以覆写recoveryAttempter方法localizedRecoveryOptions必须返回一个按钮标题数组(包括请求错误恢复的按钮的标题). 可以将数组作为NSLocalizedRecoveryOptionsErrorKey的值添加到user info字典中也可以覆盖localizedRecoveryOptions方法
Handling Received Errors
注意 本章中描述的错误表示和错误响应程序体系结构仅在Mac上可用
当将presentError:或presentError:modalForWindow:delegate:didPresentSelector:contextInfo:消息发送给某些合格对象时, 该消息将在称为错误响应者链的应用程序中沿一系列对象传播. 该链中大多数对象的默认实现是在将presentError:消息发送到下一个对象之前, 将willPresentError:方法发送给self. willPresentError:消息使自定义子类的实例有机会查看链上传递的错误对象并可能对其进行自定义. 错误对象到达链的末尾时, 全局应用程序对象NSApp向用户显示错误alert. 但在NSApp显示错误警报之前, 它会调用方法application:willPresentError:为其委托提供相同的机会
Passing Errors Up the Error-Responder Chain
如果有NSDocument, NSDocumentController, NSWindowController, NSWindow, NSPanel或任何视图类的子类, 则可以重写willPresentError:方法以自定义错误的表示形式. 如果子类实例比应用程序中的其他对象更了解特定错误的上下文则可能需要执行此操作. 通常willPresentError的实现: 检查传入的NSError对象, 例如如果其本地化描述不足, 或者如果子类知道如何从错误中恢复, 它将创建一个新的NSError对象并返回它. 在大多数情况下, 自定义错误对象会保留传入对象中的某些信息
willPresentError:方法的实现应始终使用错误域和错误码作为决定是否返回自定义错误对象的基础. 不要基于user info字典中的字符串来做出决定. 因为这些字符串可以本地化并且在调用之间可能有所不同. 如果实现决定不自定义错误, 请不要直接返回传入的对象, 而是将willPresentError:消息发送给super
处理在错误响应者链中传递的错误
- (NSError *)willPresentError:(NSError *)error {
if ([[error domain] isEqualToString:NSCocoaErrorDomain]) {
switch([error code]) {
case NSFileLockingError:
case NSFileReadNoSuchFileError:
{ // Private method of custom subclass.
return [self customizeError:error];
}
default:
return [super willPresentError:error];
}
}
return [super willPresentError:error];
}
无需创建子类即可自定义要呈现的NSError对象. 应用程序代理可以实现application:willPresentError:方法. 上面针对willPresentError:给出的相同观察和准则适用于application:willPresentError:的实现, 不同之处在于如果决定不对其进行自定义, 则可以直接返回原始错误对象
Customizing an Error Object
在上面的代码示的willPresentError:示例中调用了一个私有方法来自定义错误对象. 这样做是为了阐明实现的结构. 但是如果自定义代码是内联的则可能看起来像下面willPresentError:实现. 此代码检查传入的对象是否有失败原因, 如果确实如此, 它将创建一个更特定于应用程序的错误描述并附加失败原因. 然后它将使用此不同的描述创建一个新的NSError对象
自定义NSError对象
- (NSError *)willPresentError:(NSError *)error {
if ([[error domain] isEqualToString:NSCocoaErrorDomain]) {
switch([error code]) {
case NSFileLockingError:
case NSFileReadNoSuchFileError:
{
NSString *locFailure = [error localizedFailureReason];
if (locFailure) {
NSMutableDictionary *newUserInfo = [NSMutableDictionary
dictionaryWithCapacity:[[[error userInfo] allKeys] count]];
[newUserInfo setDictionary:[error userInfo]];
NSString *errorDesc = [NSString stringWithFormat:
NSLocalizedString(@”MyGreatApp cannot open the file. %@”, @””),
locFailure];
[newUserInfo setObject:errorDesc
forKey:NSLocalizedDescriptionKey];
NSError *newError = [NSError errorWithDomain:[error domain]
code:[error code] userInfo:newUserInfo];
return newError;
}
else {
return [super willPresentError:error];
}
}
default:
return [super willPresentError:error];
}
}
return [super willPresentError:error];
}
在此示例中, 原始错误对象实质上是被克隆以创建新的对象. 新的错误对象包含更具体的错误描述, 并向其附加故障原因
Recovering From Errors
注意 本章中描述的错误表示, 错误响应程序和错误恢复体系结构仅在Mac上可用
如恢复尝试中所述, NSError对象可以具有指定的恢复尝试者,该对象将在用户请求时尝试从错误中恢复. 错误对象在其user info字典中保留了对恢复尝试程序的引用. 因此如果错误对象在应用程序中传递则恢复尝试程序将保留在该对象中. user info字典还必须包含恢复选项, 按钮标题的本地化字符串数组, 其中一个或多个请求恢复. 当错误出现在alert中并且用户选择了恢复选项时将向恢复尝试者发送一条消息, 要求它执行其工作.
理想情况下恢复尝试程序应该是一个独立的对象, 该对象了解有关错误状况以及如何最好地规避这些状况. 应用程序甚至可能具有一个对象其作用是从各种错误中恢复, 恢复尝试者必须实现NSErrorRecoveryAttempting非正式协议的两种方法中的至少一种:attemptRecoveryFromError:optionIndex:delegate:didRecoverSelector:contextInfo: 或者attemptRecoveryFromError:optionIndex:.
还必须准备一个错误对象, 以便可以进行错误恢复. 为此,需要将三个项目添加到错误对象的user info字典中
NSRecoveryAttempterErrorKey键入的恢复尝试对象NSLocalizedRecoveryOptionsErrorKey键入本地化字符串数组NSLocalizedRecoverySuggestionErrorKey键入已本地化的恢复建议字符串- 尽管不是严格要求此属性但是按照人机界面指南的规定, 提供恢复选项的错误
alert应显示此字符串. 而且如果该字符串引用了特定的按钮标题, 则应在recovery-options数组中使用相同的标题
- 尽管不是严格要求此属性但是按照人机界面指南的规定, 提供恢复选项的错误
如下代码说明了涉及NSXMLDocument类的情况. 在此示例中NSDocument对象尝试使用NSXMLDocument的initWithContentsOfURL:options:error:方法创建表示XML文档的内部树. 如果尝试失败, 通常的原因是源XML格式错误-例如元素缺少结束标记, 或者未引用属性值. 如果首先"整理"源XML以解决结构性问题则有可能创建XML树.
示例代码中, 如果initWithContentsOfURL:options:error:的调用通过引用返回错误对象, 则文档对象将自定义错误对象, 并在其user info中添加(其中包括)恢复尝试器对象, 本地化恢复选项和本地化恢复建议字典. 然后它将presentError:modalForWindow:delegate:didPresentSelector:contextInfo:发送给self
准备错误恢复
- (BOOL)readFromURL:(NSURL *)furl ofType:(NSString *)type error:(NSError **)anError {
NSError *err;
// xmlDoc is an NSXMLDocument instance variable.
if (xmlDoc != nil) {
xmlDoc = nil;
}
xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:furl
options:NSXMLNodeOptionsNone error:&err];
if (xmlDoc == nil && err) {
NSString *newDesc = [[err localizedDescription] stringByAppendingString:
([err localizedFailureReason] ? [err localizedFailureReason] : @"")];
NSDictionary *newDict = @{ NSLocalizedDescriptionKey : newDesc,
NSURLErrorKey : furl,
NSRecoveryAttempterErrorKey : self,
NSLocalizedRecoverySuggestionErrorKey :
NSLocalizedString(@"Would you like to tidy the XML and try again?", @""),
NSLocalizedRecoveryOptionsErrorKey :
@[NSLocalizedString(@"Try Again", @""), NSLocalizedString(@"Cancel", @"")] };
NSError *newError = [[NSError alloc] initWithDomain:[err domain]
code:[err code] userInfo:newDict];
[self presentError:newError modalForWindow:[self windowForSheet]
delegate:self
didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
contextInfo:nil];
}
// ...
请注意文档对象还将向user info字典添加标识XML来源的URL. 恢复尝试程序尝试创建代表XML的树时将使用此URL.
错误对象在错误响应者链中传递并由NSApp显示. 当用户单击错误alert的任何按钮时, NSApp会检查错误对象是否同时具有恢复尝试程序和恢复选项. 如果这两个条件都成立它将调用由恢复尝试程序实现的与alert模式(document-modal or application-modal)相对应的方法
下面代码显示了XML文档的恢复尝试程序如何实现tryRecoveryFromError:optionIndex:delegate:didRecoverSelector:contextInfo:方法
从错误中恢复并通知模态代理
- (void)attemptRecoveryFromError:(NSError *)error
optionIndex:(unsigned int)recoveryOptionIndex
delegate:(id)delegate
didRecoverSelector:(SEL)didRecoverSelector
contextInfo:(void *)contextInfo {
BOOL success = NO;
NSError *err;
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:
[delegate methodSignatureForSelector:didRecoverSelector]];
[invoke setSelector:didRecoverSelector];
if (recoveryOptionIndex == 0) { // Recovery requested.
xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:[[error userInfo]
objectForKey:NSURLErrorKey] options:NSXMLDocumentTidyXML error:&err];
if (xmlDoc != nil) {
success = YES;
}
}
[invoke setArgument:(void *)&success atIndex:2];
if (err)
[invoke setArgument:&err atIndex:3];
[invoke invokeWithTarget:delegate];
}
上面示例的关键部分是恢复尝试者进行的测试, 以确定用户是否单击了"重试"按钮:它检查recoveryOptionIndex的值. 如果用户确实单击了此按钮, 则恢复尝试程序将再次使用NSXMLDocumentTidyXML选项调用initWithContentsOfURL:options:error:方法. 然后它创建并调用NSInvocation对象从而将所需消息发送到错误alert的模态代理. 调用对象包括委托者的选择器需要的两个参数:指示恢复尝试是否成功的布尔值和"**上下文信息"**参数. 在这种情况下, 该参数包含恢复尝试返回的任何错误对象.
注意 代码中的示例显示了如何使用NSInvocation发送消息. 但是如果有对模态代理的引用, 并且知道其实现的方法的名称则可以直接发送消息
当模态代理收到来自恢复尝试程序的消息时,如下代码它可以适当地响应
- (void)didPresentErrorWithRecovery:(BOOL)didRecover
contextInfo:(void *)contextInfo {
NSError *theError = (NSError *)contextInfo;
if (didRecover) {
[tableView reloadData];
} else if (theError && [theError isKindOfClass:[NSError class]]) {
[NSAlert alertWithError:theError];
}
}
理解如有错误 望指正 转载请说明出处