Programming with Objective-C(七)

这一篇的主要内容是错误处理,不过苹果另外有一篇专门介绍错误处理的文档,所以本篇更像是 OC 中错误处理的一个简单介绍。

首先谈谈错误处理的重要性,其实一般来说很少有人可以保证自己的软件是没有错误的。或者说,即使能够保证自己出错,也很难保证别人不出错。即便当前没有错误,也很难说以后会不会出错。如果直接忽视可能出现的错误,把一款随时都可能崩溃的软件交给用户,那么无疑用户体验会非常糟糕,所以我们需要提前做好一定准备的错误处理。

编程中出现的错误,大体可以分为两类,编译时错误和运行时错误。对于编译时错误,其实没什么好担心的,因为编译器会在编译前就告知我们。最难缠的就是运行时错误,有时候是因为粗心导致的,比如越界,除零等。但是有时候是疏忽了,或者逻辑上考虑有问题导致的,这类错误就很难预先知道,出现错误之后只能通过调试来解决。当然,我们可以做好预防工作,比如,在关键的位置我们一开始就做好日志的输出,这样错误一出现我们就可以知道大致的位置。不过这依旧难以避免有时候因为错误导致的程序崩溃,所以我们还是需要错误处理,以在程序出现错误的时候能够做一定程度的自我修复。

在 OC 中谈到错误处理,第一点需要注意的就是空指针。因为 OC 是一门动态语言,所以对空指针发送消息也是可以的,但是找不到方法的时候就会出现错误,这个还挺难发现,所以在编写代码的时候就要有意识的检查是否为空。

另外,在 OC 中其实也有我们很熟悉的 Exception,不过苹果官方并不怎么推荐使用,Exception 在 OC 中主要就是用于一些编程时的错误,比如越界之类的。相对的,苹果也为我们提供了用于 Cocoa 以及 Cocoa Touch 的错误处理类,NSError。

NSError

关于苹果为什么这么喜欢 NSError,我觉得可能是规范问题,因为以往的 Exception,我们都是简单的给出问题的描述就结束了,但是 NSError 有些不同,它有着自己的规范,我们可以从官方文档给出的一些例子中来看看。

假如我们希望从远程服务器上请求数据,在这个过程中,可能会产生很多问题:

  • 当前设备没有连接到网络
  • 远程服务器无法访问
  • 远程服务器提供的数据可能存在错误
  • 收到的数据和预期存在差异

对于这些情况,很难说预先就可以考虑好一切,比如数据不对我们就很难说直接预先写好代码来纠正数据,所以这个时候的处理就是放出 error 的信息,然后根据 error 的类型来进行一定的处理,尽可能不要影响到用户体验。

NSError 的简单使用

在 OC 中,我们经常会用到 delegate,一般都是把数据处理之类的工作交给 delegate 来实现,然后控制器只负责获取数据,视图用于显示。这种情况下,往往出现的错误,都要是由 delegate 对象来告诉我们。这种时候,我们就可以在 delegate 对象中实现一个带有错误对象的方法,我们可以用网络连接的类作为一个例子来看看。比如 NSURLConnectionDelegate 协议中,我们可以看到有一个 connection:didFailWIthError: 方法:

    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

在这个方法中,我们直接传了一个 NSError 对象进去,如果说在连接 URL 的过程中出现了问题,那么这个方法会在 NSError 对象中加入对问题的详细描述。

NSError 对象主要由三个部分组成,第一个部分是错误代码,第二个部分是错误领域,第三个部分就是错误的描述。刚接触的时候可能会有点疑惑,有了代码为什么我们还需要一个领域呢?实际上,在编程中间可能出现的错误种类非常多,如果全部都用错误代码来标识,那么代码的值会变得非常得大,在个人查找的时候是非常麻烦的,同时,自定义的错误就非常不好处理了,一是因为容易和系统定义的代码产生冲突,二是如果当前没有冲突,也很难保证以后官方再更新一堆代码出来是不是又出现错误了。所以错误领域可以先进行一定程度的划分,这样我们在查找错误代码的时候就非常方便,并且自定义的时候,自己定义一个领域,就不会轻易产生冲突。当然错误域的用法还有一个,就是多人协作的时候,不同人之间去自定义一个错误,也可能产生错误,那么我们直接按照组来分,然后每个组采取一个固定的标识,比如就是一个自己取的组名,就可以轻松解决这个问题。

接下来我们看官方给出的另外一个例子,有时候可能我们需要对数据进行持久化,这个时候,我们需要调用 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
}

当错误出现的时候,这个方法就会返回 NO,然后我们就可以输出错误信息,并且做一定的处理了。

错误处理

苹果官方给我们的建议就是,当错误出现了之后,要么我们想办法恢复,要么就告知用户出现了错误,其实就是要多注意一下用户体验。比如我们请求服务器数据的时候,我们可能存在几个备选的服务器,那么最开始请求失败了我们第一考虑的就是去备用服务器上请求数据,因为这样有可能程序可以正常运行。但是服务器全部挂了这就无能为力了,这时候我们就应该提示一下用户,告诉他们服务器出现问题了。不然用户一直刷新又得不到相应,那用户体验肯定要大打折扣了。所以整体来说,对于错误就是先采取补救措施,看能不能让程序继续正常运行,但是如果实在不行,至少也得告诉用户。

自定义错误

不管官方怎么去定义错误,但是总有很多错误是和我们自己所开发的项目相关的,单靠官方定义的一系列错误,很多时候是没有办法满足我们的需求的。所以,我们需要自定义错误。自定义错误的第一步,就是定义一个错误域,为什么需要错误域已经说过了,关键就是错误域怎么去定义,其实这个也提到了,我们完全可以用一个包名来避免冲突。不过,苹果官方其实是很重视规范的,所以他们也给出了一个错误域的定义规范:

com.companyName.appOrFrameworkName.ErrorDomain

就像上面这样的一个字符串,其实它和包名还是挺像的,com 开头,然后是公司名,接下来是应用名或者框架名,然后就是 ErrorDomain,其实前面的两个部分就足以区分应用间的错误了,如果有必要的话,可以再把 ErrorDomain 改成自己需要的名称。

有了错误域之后,我们还需要错误代码,简单点说就是一个错误大类下面可能会存在具体的不同种类的错误,这就是错误代码的作用,这里定义的时候就注意不要冲突就好了。其实这么做的话,一个小组开发的一个应用是可以直接定义一个文件声明静态的变量用于表示错误代码,用的时候直接取出来就行了。再回过头来看自定义的错误,第三部分是对错误的描述,这个描述只会存在 userinfo 字典中的:

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** 而不是 NSError*,这里其实并不是写错什么的,而是相应的函数中,需要通过引用来生成一个 NSError 对象:

- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr;

当错误出现的时候,我们就先进行检查,防止这个引用为空,然后我们就可以生成一个自定义的错误:

- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr {
...
// error occurred
if (errorPtr) {
    *errorPtr = [NSError errorWithDomain:...
                                    code:...
                                userInfo:...];
}
return NO;

}

异常

尽管苹果官方并不推荐我们使用异常,但这并不意味着 OC 中就没有异常,实际上,异常在 OC 中也是经常会遇到的,毕竟我们写程序的时候也会遇到程序崩溃的时候,这时候去看日志就能看到其实是出现了异常。异常的使用其实和其他的语言是差不多的:

@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
}

总结

其实很多人看到苹果的错误处理总是觉得很奇怪,尤其是从其他语言转过来的时候,总是喜欢去使用异常。这个地方其实还是苹果官方的问题,苹果官方是非常在意规范的,所以苹果的整体的命名,开发的形式,一旦上手之后写起来就会觉得很顺。对于错误和异常,苹果实际上还是把他们区分掉了,异常往往是一些致命的和语言相关的错误,比如说我们的越界访问以及调用了对象没有的函数等问题,这类问题一是会造成程序崩溃,二是它们其实是语言上的一些特性造成的。在苹果看来,用户个人编写代码造成的错误,往往是因为逻辑上的,或者和个人的应用相关的错误,这些错误和语言中的一些错误混在一起,在苹果官方看来并不是很好,所以他们使用 NSError 来让用户把逻辑上的,和自己应用相关的一些错误给放出来。我们也可以看到,苹果官方 API 中,和 NSError 相关的方法,基本上都不会因为传进去的参数直接导致程序崩溃,所以其实 NSError 相当于是苹果独立出来专门给开发者使用的错误处理,异常更多的是官方准备好的应对程序崩溃的措施。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值