来源于 Ry’s Objective-C Tutorial - RyPress

一个学习Objective-C基础知识的网站.

个人觉得很棒,所以决定抽时间把章节翻译一下.

本人的英语水平有限,有让大家误解或者迷惑的地方还请指正.

原文地址:http://rypress.com/tutorials/objective-c/exceptions

仅供学习,如有转摘,请注明出处.


iOS或者OS X程序运行中,会产生两种不同类型的问题.其中,异常代表着编程人员级别的bugs,比如尝试访问不存在的数组元素.它们被设计的目的就是告知开发人员 - 有意外的情况发生.异常一般很少在你写代码时发生,却通常导致程序崩溃.
相对于异常,错误则是用户级别的问题,比如尝试加载一个不存在的文件.因为错误是正常程序执行时预期的,所以在这类错误发生时,你应该手动核查这类情况并告知用户.大部分情况下,错误不会引起程序崩溃.

 
Exceptions vs. errors

该部分将对异常和错误进行深入的介绍.从概念上来说,处理异常与处理错误很相似.首先,你得检测到问题,然后再处理它.随后我们会看到它们之间的不同.

异常

异常对应的类是NSException.它被设计成(用)一个通用的方式来封装了异常数据,所以你基本上不用子类化它或者定义一个自定义的异常对象.下面列出了NSException的三个主要属性.

属性 描述
name NSString类型,唯一标识该异常
reason NSString类型,可读的异常信息描述
userinfo NSDictionary类型,其中的键值对包含有关异常的额外信息,取决于异常类型

异常仅用于严重的程序错误,理解这点很重要.这个是让你知道,一些问题在开发周期中产生,在你去解决这个问题之后,是不会再发生了.而如果处理一个可预测的问题,那你应该用错误,而不是异常.

异常处理

在大多数的高级编程语言中,都能通过使用常规的try-catch-finally方式来处理异常.首先,将可能产生异常的代码放在@try块中,随后,如果抛出异常,对应的@catch()块便会执行以便处理发生的问题.@finally块在最后执行,无论是否产生异常,@finally块都会执行.

下面的main.m文件通过访问一个数组不存在元素来触发一个异常.在@catch()块中,我们仅简单的显示了异常详细内容.括号里的NSException对象*theException是包含异常对象的变量名称.

// main.m
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *inventory = @[@"Honda Civic",
@"Nissan Versa",
@"Ford F-150"];
int selectedIndex = 3;
@try {
NSString *car = inventory[selectedIndex];
NSLog(@"The selected car is: %@", car);
} @catch(NSException *theException) {
NSLog(@"An exception occurred: %@", theException.name);
NSLog(@"Here are some details: %@", theException.reason);
} @finally {
NSLog(@"Executing finally block");
}
}
return 0;
}

在实际情况下,你会想在@catch()块中,通过打印问题并修正它来处理异常,或者构造一个错误对象并展示给用户.(对于)处理未捕获的异常,默认的行为是在控制台输出(相关)信息并退出程序.

OC的异常处理能力并不是最高效的,所以你应该仅在真正特殊(例外)的情况下使用@try/@catch()块.不要用它来代替普通的的控制流.相反的,使用标准的if语句来核查可预测的情况.

这意味着上述的代码对异常的使用很不恰当.一个更好的方式是应该使用惯用的比较方式来确认selectedIndex得比[inventory count]小.

if (selectedIndex < [inventory count]) {
NSString *car = inventory[selectedIndex];
NSLog(@"The selected car is: %@", car);
} else {
// Handle the error
}
内置的异常(对象)

标准的iOS和OS X框架中定义了几种内置的异常.完整的(异常)列表可在这里查看,但最常用的一些如下所示:

异常名称 描述
NSRangeException 访问集合外界元素时产生
NSInvalidArgumentException 给方法传递非法参数时产生
NSInternalInconsistencyException 内部发生意外情况产生
NSGenericException 当你不知道是什么触发异常时产生

请注意,这些值都是strings,不是NSException的子类.所以,当你想查看异常的特定类型时,你需要像下面这样查看(异常的)name属性才行:

...
} @catch(NSException *theException) {
if (theException.name == NSRangeException) {
NSLog(@"Caught an NSRangeException");
} else {
NSLog(@"Ignored a %@ exception", theException);
@throw;
}
} ...

在@catch块中,@throw指令重新产生一个已捕获的异常.在上述代码中,我们使用@throw来将异常抛到更高层的@try块中,以便我们忽略我们不想处理的异常.一个简单的if语句还是很有必要的.

自定义异常

可以使用@throw来抛出包含自定义数据的异常对象.最容易的方式就是通过 exceptionWithName:reason:userinfo 工厂方法创建一个异常实例.下面的例子是在top-level函数中抛出异常并在mian()函数中捕获:

// main.m
#import <Foundation/Foundation.h> NSString *getRandomCarFromInventory(NSArray *inventory) {
int maximum = (int)[inventory count];
if (maximum == 0) {
NSException *e = [NSException
exceptionWithName:@"EmptyInventoryException"
reason:@"*** The inventory has no cars!"
userInfo:nil];
@throw e;
}
int randomIndex = arc4random_uniform(maximum);
return inventory[randomIndex];
} int main(int argc, const char * argv[]) {
@autoreleasepool {
@try {
NSString *car = getRandomCarFromInventory(@[]);
NSLog(@"The selected car is: %@", car);
} @catch(NSException *theException) {
if (theException.name == @"EmptyInventoryException") {
NSLog(@"Caught an EmptyInventoryException");
} else {
NSLog(@"Ignored a %@ exception", theException);
@throw;
}
}
}
return 0;
}

除非必要,否则你不应该在常规程序中这样去抛出自定义异常.其中一个原因就是,异常代表了编程人员的错误,而且应该在当你为严重的编码错误做打算时,才考虑使用它.其二,@throw是一个昂贵的操作,尽可能的使用errors更好.

错误

错误代表着可预料的问题,并且有很多类型的操作可以在不引起程序崩溃的的情况下失败,它们比异常更常见.与异常不同,这种错误核查是高质量代码的常规项.
NSError类封装了失败操作的详细内容.它的主要属性与NSException类似.

属性 描述
domain NSString类型,包含了错误的domain.被用来将错误组织成层级结构并且保证错误码不会冲突
code NSInteger类型,标识了error的ID.在相同domain中的每个error都有一个唯一的值
userInfo NSDictionary类型,其中的key-value对包含了错误的额外信息, (键值对内容)取决与错误类型

NSError对象的userInfo字典比NSException的字典版本提供了更多内容.一些预定义的键被定义为常量,如下表:

NSLocalizedDescriptionKey NSString类型,代表着错误的全部描述.通常也包含了失败原因
NSLocalizedFailureReasonErrorKey NSString类型,简洁的错误原因描述
NSUnderlyingErrorKey 对代表着下一高层次的domain中的错误的另一个NSError引用

根据错误(情况), 这个字典也包含其他特殊的domain信息.比如, 文件加载错误对应的key是NSFilePathErrorKey,它(对应的value)包含了所请求文件的路径.

注意,localizedDescription和localizedFailureReason方法是分别访问头两个key的可选方式.下面的章节使用了它们.

错误处理

错误不需要任何像@try,@catch这样的专用的语言指令.相反地,函数或者方法失败之后会接受到一个额外的参数(通常被称作Error),(这个error)指向了NSError对象.如果一个操作失败了,一般会返回NO或者nil来标明错误并且把这个(额外的)参数填充到错误详情中.如果成功,则简单返回正常的请求值.

很多方法都被配置成能接受一个间接的NSError对象引用.一个间接的引用是一个指针的指针,它允许方法的这个参数指向一个全新的NSError实例.你可以通过两个指针记号[(NSError **)error]来决定一个方法的error参数是否接受一个间接的引用.

随后的代码段,通过NSString类的stringWithContentsOfFile:encoding:error:方法来尝试加载不存在文件以证明这种错误处理模式.当文件加载成功,这个方法以NSString类型返回文件的内容,但当加载失败,便会直接返回nil,同时返回已填充新的NSError对象的error参数.

// main.m
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *fileToLoad = @"/path/to/non-existent-file.txt"; NSError *error;
NSString *content = [NSString stringWithContentsOfFile:fileToLoad
encoding:NSUTF8StringEncoding
error:&error]; if (content == nil) {
// Method failed
NSLog(@"Error loading file %@!", fileToLoad);
NSLog(@"Domain: %@", error.domain);
NSLog(@"Error Code: %ld", error.code);
NSLog(@"Description: %@", [error localizedDescription]);
NSLog(@"Reason: %@", [error localizedFailureReason]);
} else {
// Method succeeded
NSLog(@"Content loaded!");
NSLog(@"%@", content);
}
}
return 0;
}

注意我们是怎样被迫通过引用操作来将error变量传递给这个方法的.因为error参数接收一个指针引用(双指针).也注意我们是怎样使用普通的if语句来核查方法成功情况下的返回值.你应该只在方法直接返回nil的时候再去访问NSError引用,而且,你永远都不应该使用NSError对象的存在来判断(方法调用的)成功或失败.

当然,如果你仅关注的操作的成功而不考虑为何失败,那你只要给error参数传递一个NULL,它就会被忽略了.

内置错误

与NSException类似,NSError也被设计成一个用来表示错误的通用对象.与子类化它相反,各种iOS与OS X框架都为domain和code fields定义了它们自己的常量.有很多内置的错误domain,主要的四个如下:

  • NSMachErrorDomain
  • NSPOSIXErrorDomain
  • NSOSStatusErrorDomain
  • NSCocoaErrorDomain

大部分你遇到的错误都在NSCocoaErrorDomain中,但如果你继续往低层次的domain中探究,你会看到其他一些.比如,如果你把下面一行加入到main.m中,你将会发现一个NSPOSIXErrorDomain的错误.

NSLog(@"Underlying Error: %@", error.userInfo[NSUnderlyingErrorKey]);

对于大多数应用来说,你不必这么做,但是当你需要知道引起错误的根源时,它就能派上用场了.

在你确定了错误domain后,你可以核查一下具体的错误码.Foundation Constants Reference描述了一些枚举,其中的大部分错误码都在NSCocoaErrorDomain中定义了.比如下面的代码,用来判断错误是否是NSFileReadNoSuchFileError.

...
if (content == nil) {
if ([error.domain isEqualToString:@"NSCocoaErrorDomain"] &&
error.code == NSFileReadNoSuchFileError) {
NSLog(@"That file doesn't exist!");
NSLog(@"Path: %@", [[error userInfo] objectForKey:NSFilePathErrorKey]);
} else {
NSLog(@"Some other kind of read occurred");
}
} ...

其他框架应该在他们的文档中包含任何自定义的domain和错误码.

自定义错误

如果你正在参与一个大的项目,你很可能会有一些函数或者方法导致错误.这部分章节将说明如何使用上述的典型的错误处理模式来配置它们.

作为最佳实践,你应该在专门的头文件中定义你的错误.举例来说,InventoryErrors.h文件可以定义一个domain,包含了与inventory中项目相关的各类的不同错误码.

// InventoryErrors.h

NSString *InventoryErrorDomain = @"com.RyPress.Inventory.ErrorDomain";

enum {
InventoryNotLoadedError,
InventoryEmptyError,
InventoryInternalError
};

从技术上来说,自定义错误domain可以定义成任何你想的,但推荐的形式是com.<Company>.<Framework-or-project>.ErrorDomain,正如在InventoryErrorDomina.h中所示的.(并利用)枚举定义了错误码常量.

关于函数和方法的区别就在于是否支持额外的error参数.它是特定的NSError **类型,如下所示的getRandomCarFromInventory().当发生一个错误,你会将这个参数指向一个新的NSError对象.需要注意我们是怎样手动将NSLocalizedDescriptionKey添加到userInfo字典中来定义localizedDescription的.

// main.m
#import <Foundation/Foundation.h>
#import "InventoryErrors.h" NSString *getRandomCarFromInventory(NSArray *inventory, NSError **error) {
int maximum = (int)[inventory count];
if (maximum == 0) {
if (error != NULL) {
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Could not"
" select a car because there are no cars in the inventory."}; *error = [NSError errorWithDomain:InventoryErrorDomain
code:InventoryEmptyError
userInfo:userInfo];
}
return nil;
}
int randomIndex = arc4random_uniform(maximum);
return inventory[randomIndex];
} int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *inventory = @[];
NSError *error;
NSString *car = getRandomCarFromInventory(inventory, &error); if (car == nil) {
// Failed
NSLog(@"Car could not be selected");
NSLog(@"Domain: %@", error.domain);
NSLog(@"Error Code: %ld", error.code);
NSLog(@"Description: %@", [error localizedDescription]); } else {
// Succeeded
NSLog(@"Car selected!");
NSLog(@"%@", car);
}
}
return 0;
}

因为从技术上它是一个错误,而不是异常,这个getRandomCarFromInventory()版本是一个更"适当"的方式来处理它.(与Custom Exceptions对应).

总结

错误代表着iOS或者OS X应用的失败操作.它是一个标准的方式,用来记录检测点的相关信息并且将它传给处理代码.异常(与错误)也比较类似,但被设计成更多用于开发辅助.它们通常都不应该用于 production-ready[已成品的] 程序中.

怎么处理错误或者异常很大程度上都得根据问题类型以及你的应用程序才能决定.但是,大多数情况都会使用像 UIAlertView(iOS). or NSAlert(OS X)(控件)这种来告知用户信息.之后,你很可能想通过检查NSError或者NSExcepiton对象来发掘问题所在,从而尝试修复它.

下个章节探讨一下关于OC运行时的比较偏概念的东西.我们将学习有关对象背后的内存是怎样通过手动retain,release进行管理的(目前已过时),以及目前新的ARC实际含义.


写于15年09月29号, 完成于15年10月20号
这一片感觉翻译的很烂, 请留情...不甚感激

作者:yiron
链接:https://www.jianshu.com/p/3c0c4ffaaa7d
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Exceptions & Errors - 异常与错误的更多相关文章

  1. Python基础之:Python中的异常和错误

    目录 简介 Python中的内置异常类 语法错误 异常 异常处理 抛出异常 异常链 自定义异常 finally 简介 和其他的语言一样,Python中也有异常和错误.在 Python 中,所有异常都是 ...

  2. 扩展Python模块系列(五)----异常和错误处理

    在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题.重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_ ...

  3. chrome console的使用 : 异常和错误的处理 – Break易站

    本文内容来自:chrome console的使用 : 异常和错误的处理 – Break易站 利用 Chrome DevTools 提供的工具,您可以修复引发异常的网页和在 JavaScript 中调试 ...

  4. How Not to Crash #2: Mutation Exceptions 可变异常(不要枚举可变的集合)

    How Not to Crash #2: Mutation Exceptions 可变异常html, body {overflow-x: initial !important;}html { font ...

  5. ASP.NET Core 中间件Diagnostics使用 异常和错误信息

    ASP.NET Core 中间件(Middleware)Diagnostics使用.对于中间件的介绍可以查看之前的文章ASP.NET Core 开发-中间件(Middleware). Diagnost ...

  6. 《java中异常和错误》

    异常和错误的区别. 异常: 在Java中程序的错误主要是语法错误和语义错误,一个程序在编译和运行时出现的错误我们统一称之为异常,它是VM(虚拟机)通知你的一种方式,通过这种方式,VM让你知道,你(开发 ...

  7. 拦截PHP各种异常和错误,发生致命错误时进行报警,万事防患于未然

    在日常开发中,大多数人的做法是在开发环境时开启调试模式,在产品环境关闭调试模式.在开发的时候可以查看各种错误.异常,但是在线上就把错误显示的关闭. 上面的情形看似很科学,有人解释为这样很安全,别人看不 ...

  8. 关于Java异常和错误的几个问题

    1.Java中什么是Exception? 异常是Java传达给你的系统和程序错误的方式. 在java中,异常功能是通过实现比如Throwable,Exception,RuntimeException之 ...

  9. windows CE 6.0编译报BLDDEMO: There were errors building MY283错误解决办法

    今天开始正式进入windows ce程序开发. 第一次编译windows ce6.0的系统,25分钟编译后报:BLDDEMO: There were errors building MY283 错误. ...

随机推荐

  1. 何时、怎样开启 MySql 日志?

    假如你是一名 web 开发者.假设你想调试你的应用或提升其性能的话,那你须要去參考各种日志文件.日志是開始故障排除最好的选择.就著名的 MySql 数据库server而言,你须要參考下面日志文件: 错 ...

  2. div拖拽缩放jquery插件编写——带8个控制点

    项目中需要对div进行拖拽缩放,需要有控制面板8个控制点的那种,原以为这么常见的效果应该能搜索到很多相关插件,然而可以完成拖拽的实繁,却找不到我想要的,还是自己动手丰衣足食吧 效果预览(只支持pc端) ...

  3. 中文分词实践(基于R语言)

    背景:分析用户在世界杯期间讨论最多的话题. 思路:把用户关于世界杯的帖子拉下来.然后做中文分词+词频统计,最后将统计结果简单做个标签云.效果例如以下: 兴许:中文分词是中文信息处理的基础.分词之后.事 ...

  4. (七)Java 变量类型

    Java 变量类型 在Java语言中,所有的变量在使用前必须声明.声明变量的基本格式如下: type identifier [ = value][, identifier [= value] ...] ...

  5. UVA-10163 Storage Keepers DP

    题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...

  6. 【LNOI 2014】 LCA

    [题目链接] 点击打开链接 [算法] 考虑求lca(x,y)的深度 我们可以将从根到x路径上的点都打上标记,然后,询问y到根上路径的权值和 那么,求sigma(depth(lca(i,z)))(l & ...

  7. 7 Worksheet 对象

    7.1 设置阶段 代码清单7.1:使用Parent属性获得一个对象的父对象的指针 '使用Parent属性获得一个对象的父对象的指针 Sub MeetMySingleParent() 'Declare ...

  8. CF 1042 A Benches —— 二分答案(水题)

    题目:http://codeforces.com/problemset/problem/1042/A 代码如下: #include<iostream> #include<cstdio ...

  9. 洛谷P1291 [SHOI2002]百事世界杯之旅——期望DP

    题目:https://www.luogu.org/problemnew/show/P1291 水水的经典期望DP: 输出有毒.(其实也很简单啦) 代码如下: #include<iostream& ...

  10. 常用开源<监控软件>介绍

    转载地址:http://blog.csdn.net/lx_9986/article/details/6803243 一.Zenoss Core Zenoss Core是开源企业级IT管理软件-是智能监 ...