一同养成写作习惯!这是我参加「日新方案 4 月更文应战」的第2天,点击查看活动详情。

探求系列已发布文章列表,有兴趣的同学能够翻阅一下:

第一篇 | iOS 特点 @property 具体探求

第二篇 | iOS 深化了解 Block 运用及原理

第三篇 | iOS 类别 Category 和扩展 Extension 及关联目标详解

第四篇 | iOS 常用锁 NSLock ,@synchronized 等的底层完成详解

第五篇 | iOS 全面了解 Nullability

第六篇 Equality(即 ==,isEqual,isEqualToString)具体探求

——- 正文开始 ——-

引言

Objective-C 言语具有类似于 JavaC++ 的反常处理语法。经过将此语法与 NSExceptionNSError 或自界说类一同运用,咱们能够为程序添加健壮的过错处理。本文介绍一下反常语法的运用及怎么处理反常状况。


  • 常用介绍

  • 启用反常处理

GNU Compiler Collection (GCC) 3.3 及更高版别开始对 Objective-C 供给言语级反常处理支撑。只需求翻开对这些功能的支撑 -fobjc-exceptions 开关。(留意: 此开关使应用程序只能在 OS X v10.3 及更高版别中运行,因为早期版别的软件中不存在对反常处理和同步的运行时支撑。)

  • 反常处理

反常是中止正常程序履行流程的特殊状况。硬件和软件或许产生反常的原因有许多(一般称为引发或抛出反常)。包括算术过错,例如被零除、下溢或溢出、调用未界说的指令(例如测验调用未完成的办法)以及测验越界访问集合元素等等。

Objective-C 反常支撑触及四个编译器指令:@try@catch@throw@finally

或许引发反常的代码包括在 @try{} 块中。@catch{} 块包括在 @try{} 块中抛出的反常的反常处理逻辑。咱们能够有多个 @catch{} 块来捕获不同类型的反常。

当咱们运用 @throw 指令抛出一个反常,它本质上是一个Objective-C 目标。咱们一般运用 NSException 目标,但这不是有必要的。

@finally{} 块包括无论是否引发反常都有必要履行的代码。下面例子描述了一个简略的反常处理:

Cup *cup = [[Cup alloc] init];
@try {
    [cup fill];
}
@catch (NSException *exception) {
    NSLog(@"main: Caught %@: %@", [exception name], [exception reason]);
}
@finally {
    // [cup release];
}
  • 捕获不同类型的反常

要捕获 @try{} 块中引发的反常,需求在 @try{} 块之后运用一个或多个 @catch{} 块。@catch{} 块应按从最具体反常到最不具体反常的顺序排列。这样,咱们能够将反常处理定制为组,如下面的一个反常处理程序:

@try {
    ...
}
@catch (CustomException *ce) {   // 1
    ...
}
@catch (NSException *ne) {       // 2
    // Perform processing necessary at this level.
    ...
}
@catch (id ue) {
    ...
}
@finally {                       // 3
    // Perform processing necessary whether an exception occurred or not.
    ...
}

以下对应上面代码具体位置的作用:

  1. 捕获最具体的反常类型。
  2. 捕获一般的反常类型。
  3. 无论是否引发反常,都需求履行的整理或其他处理操作。
  • 抛出反常

当需求抛出反常时,咱们需求运用适当的信息(例如反常称号和引发反常的原因)实例化一个目标,以便咱们快速定位及查找反常的原因。

NSException *exception = [NSException exceptionWithName: @"HotTeaException" reason: @"The tea is too hot" userInfo: nil];
@throw exception;

留意: 在许多环境中,反常的运用适当遍及。例如,咱们或许会抛出反常来表明程序无法正常履行,例如文件丢失或数据无法正确解析。在 Objective-C 中,反常是资源密集型。咱们不该该将反常用于一般的流控制,或许只是表明过错。相反,咱们应该运用办法或函数的回来值来表明产生了过错,并在过错目标中供给相关问题的信息。

@catch{} 块中,咱们能够运用 @throw 指令抛出捕获的反常,而无需供给参数。在这种状况下省掉参数有助于使咱们的代码更具可读性。

不只限于抛出 NSException 目标。咱们能够将任何 Objective-C 目标作为反常目标抛出。NSException 类供给了有助于反常处理的办法,但假如咱们乐意,也能够完成自己的办法。还能够承继 NSException 来完成特殊类型的反常,例如文件体系反常或通讯反常等。

  • 具体过错及反常处理

每个程序都有必要处理运行时产生的过错。例如,该程序或许无法翻开文件,或许它或许无法解析 XML 文档。一般,诸如此类的过错需求程序告诉用户,但也能够测验让程序解决过错。

Cocoa(和 Cocoa Touch)为开发人员供给了用于处理这些使命的编程东西:Foundation 中的 NSError 类和 Application Kit 中的新办法和机制,支撑应用程序中的过错处理。NSError 目标封装了特定的过错信息,包括引发过错的域(子体系)和要在过错正告中显现的本地化字符串。还答应应用程序中的各种目标细化过错目标中的信息,并或许测验从过错中康复。

留意: NSError 类在 OS X 和 iOS 上都可用。可是,过错响应和过错康复 API 和机制仅在 Application Kit (OS X) 中可用。

  • 过错目标、域和代码

Cocoa 程序运用 NSError 目标来传递用户需求了解的运行时的过错信息。在大多数状况下,程序会在对话框或 Log 中显现此过错信息。但它也或许会提示用户并要求用户测验从过错中康复或测验自行纠正过错。

NSError 目标(或许简略地说,过错目标)的核心特点是过错域、特定于域的过错代码和包括与过错相关的目标的“用户信息”字典,最重要的是描述和康复字符串。这儿要点解释一下过错目标。

  • 为什么有过错目标?

因为它们是目标,所以 NSError 类的实例比简略的过错代码和过错字符串有几个长处。它们一次封装了几条过错信息,包括各种本地化的过错字符串。NSError 目标也能够被归档和仿制,它们能够在应用程序中传递和修正。尽管 NSError 不是一个抽象类(因而能够直接运用),但咱们能够经过子类化来扩展 NSError 类。

由于分层过错域的概念,NSError 目标能够嵌入来自底层子体系的过错,然后供给有关过错的更具体和细微差别的信息。过错目标还经过保存对指定为过错康复测验者的目标的引用来供给过错康复机制。

  • 过错域

很大程度上由于历史原因,OS X 中的过错代码被隔离到域中。例如,键入为 OSStatusCarbon 过错代码起源于 OS X 之前的 Macintosh 操作体系版别。另一方面,POSIX 过错代码源自 UNIX 的各种符合 POSIX 的“风格”,例如 BSD 。Foundation 结构在 NSError.h 中声明晰以下四个首要过错域的字符串常量:

NSMachErrorDomain
NSPOSIXErrorDomain
NSOSStatusErrorDomain
NSCocoaErrorDomain

上述域常数序列表明域的一般分层,Mach 误差域坐落最低层。咱们能够经过向 NSError 目标发送域音讯来获取过错的域。

除了四个首要域之外,还有特定于结构乃至是类组或单个类的过错域。例如,Web Kit 结构在其 Objective-C 完成中具有自己的过错域 WebKitErrorDomain。在 Foundation 结构中,URL 类和 XML 类 ( NSXMLParserErrorDomain ) 一样有自己的过错域 ( NSURLErrorDomain )。NSStream 类自身界说了两个过错域,一个用于 SSL 过错,另一个用于 SOCKS 过错。

Cocoa 过错域 ( NSCocoaErrorDomain ) 包括 Cocoa 结构的一切过错代码。当然,那些结构的特定类域中的过错代码在外。这些结构不只包括 FoundationUIKitApplication Kit,还包括 Core Data 和或许的其他 Objective-C 结构。(Cocoa 结构中与 Cocoa 过错域分离的过错域是在引入后者之前界说的)

域有几个有用的目的。它们为 Cocoa 程序供给了一种办法来识别正在检测过错的 OS X 子体系。它们还有助于防止来自具有相同数值的不同子体系的过错代码之间的抵触。此外,域答应基于子体系分层的过错代码之间的因果关系;例如,NSOSStatusErrorDomain 中的过错或许在 NSMachErrorDomain 中存在潜在过错。

咱们能够创立自己的过错域和过错代码,以便在自己的结构或许自己的应用程序中运用。主张域的字符串常量选用 com.company.framework_or_app.ErrorDomain 的形式。

  • 过错代码

过错代码标识特定域中的特定过错。它是一个有符号整数,分配为程序符号的值。咱们能够经过向 NSError 目标发送代码音讯来获取过错代码。一般过错代码在每个首要域的一个或多个头文件中声明和记录。

POSIX 过错代码声明的一部分:

#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.
    }
}

能够声明自界说的过错代码在应用程序或结构中运用,但过错代码应属于咱们自己的域。永久不该该将过错代码添加到现有的,且没有“拥有”权限的域中。

  • 用户信息字典

每个 NSError 目标都有一个“用户信息”字典来保存域和代码之外的过错信息。咱们能够经过向 NSError 目标发送 userInfo 音讯来访问该字典。NSDictionary 目标相对于另一种容器目标的优势在于它是灵敏的;它乃至能够带着有关过错的自界说信息。可是一切用户信息字典都包括(或能够包括)几个与过错相关的预界说字符串和目标值。

  • 创立和回来 NSError 目标

咱们能够声明和完成自己的间接回来 NSError 目标的办法。合适 NSError 参数的办法是翻开和读取文件、加载资源、解析格式化文本等的办法。一般,这些办法不该经过 NSError 目标的存在来指示过错。相反,它们应该从办法中回来 NOnil 以指示产生了过错。回来 NSError 目标来描述过错。

假如要在此类办法的完成中经过引用回来 NSError 目标,则有必要创立 NSError 目标。咱们能够经过分配它然后运用 NSErrorinitWithDomain:code:userInfo: 办法或运用类工厂办法 errorWithDomain:code:userInfo: 来创立一个过错目标。正如这两种办法的关键字所示,有必要为初始化程序供给一个域(字符串常量)、一个过错代码(一个有符号整数)和一个包括描述性和支撑信息的“用户信息”字典。

留意:只要在咱们的办法中有过错而且办法直接回来 NO 时,才应该修正 NSError 参数。在将目标分配给它之前,承认参数是否为非 NULL,而且永久不要将 nil 分配给过错参数。

为了便于说明,下面的代码调用 POSIX 层的 open 函数来翻开文件。假如此函数回来过错,则该办法创立 NSPOSIXErrorDomain 的 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;
    }
}
  • 关于过错和反常的说明

重要的是要记住 CocoaCocoa Touch 中过错目标和反常目标之间的差异,以及何时在代码中运用其间一个或另一个。它们各自有不同的用处,不该混淆。

反常(由 NSException 目标表明)用于编程过错,例如数组越界或无效的办法参数。用户级过错(由 NSError 目标表明)用于运行时过错,例如找不到文件或无法读取某种编码的字符串时。引起反常的条件是由于编程过错;咱们应该在发版之前处理这些过错。运行时过错总是会产生。因而,咱们应该尽或许具体地向用户展现这些过错(经过 NSError 目标)。

尽管理想状况下应该在发版之前处理反常,但由于某些真正反常的状况(例如“内存不足”或“启动卷不可用”),已发版的应用程序仍然会遇到这些反常。最好让应用程序的最高层即大局应用程序(或许说体系自身)来处理这些状况。


总结

本文比较具体的介绍了反常和过错的相关技能点和细节。或许有些同学日常开发中不是特别关注这一点,但我信任假如你花些时间在这上面,今后再碰到线上问题,或许发现反常过错相关 bug 时,会变得更游刃有余。

参考资料:

  • Exception Handling

  • Introduction to Error Handling Programming Guide For Cocoa


关于技能组

iOS 技能组首要用来学习、分享日常开发中运用到的技能,一同坚持学习,坚持前进。文章库房在这儿:github.com/minhechen/i… 微信大众号:iOS技能组,欢迎联络进群学习交流,感谢阅读。

iOS 探究 | 第七篇 异常(NSException)和错误(NSError)处理详解