Objective-C中异常/返回NO/nil的最佳实践是什么?

22

我是Objective-C的新手,看到有关错误处理的不同惯例。 有异常,但也有情况下函数只应在出现问题时返回nil。

那么,如何决定使用哪个方法,以及如何处理异常和意外的返回值? 有什么最佳实践和警告标志吗?

4个回答

37

我不会确定该使用哪种方式,但以下是关于每个选项的一些信息:

异常

在Obj-C中,异常并不是真正用于控制程序流程的。根据异常处理文档

通常情况下,仅当发生程序员错误时才会抛出异常,捕获这样的异常的程序应尽快退出。

因此,我不建议仅仅为了测试方法是否正确工作而使用异常@try/@catch

您还可以选择多种处理异常的方式,除此之外还可以设置更高级别的未捕获异常处理程序

错误

错误通常有三种用法:

委托方法

一个对象可以在指定的错误处理回调函数中简单地将NSError传递给其委托:

- (void)myObject:(MyObject *)obj didFailWithError:(NSError *)error;

委托可以执行任何适当的操作,包括向用户显示消息。这种模式通常在异步委托API中使用。

输出参数

这些通常与布尔返回值一起使用:如果返回值为NO,则可以检查NSError对象以获取有关错误的更多信息。

- (BOOL)performTaskWithParameter:(id)param returningError:(out NSError **)error;

一个可能的使用模式是:

NSError *error;
if (![myObject performTaskWithParameter:@"param" returningError:&error]) {
    NSLog(@"Task failed with error: %@", error);
}

有些人更喜欢在检查布尔值之前将结果存储在变量中,例如BOOL success = [myObject perform...];。由于该模式的线性特性,最适合用于同步任务。

基于block的完成处理程序

自从引入blocks以来,这是一个相当新的模式,但非常有用:

- (void)performAsynchronousTaskWithCompletionHandler:(void (^)(BOOL success, NSError *error))handler;

使用方式如下:

[myObject performAsynchronousTaskWithCompletionHandler:^(BOOL success, NSError *error) {    if (!success) {        // ...    }}];
这种情况千差万别:有时候你看不到布尔型参数,只能看到错误;有时候处理程序块没有传递参数给它,你只需要检查对象的状态属性(例如,这就是AVAssetExportSession的工作原理)。当你想要一种基于块的方法来处理异步任务时,这种模式也非常适用。

处理错误

Mac OS X上的Cocoa拥有一个相当完备的错误处理路径。还有NSAlert的方便方法+ (NSAlert *)alertWithError:(NSError *)error;。在iOS上,虽然存在NSError类,但没有同样方便的方法来处理错误。你可能需要自己处理很多内容。

阅读错误处理编程指南以获取更多信息。

返回nil

这通常与NSError输出参数一起使用;例如,NSData的方法

+ (id)dataWithContentsOfFile:(NSString *)path
                     options:(NSDataReadingOptions)mask
                       error:(NSError **)errorPtr;
如果读取文件失败,这个方法会返回nil,更多的信息将被保存在错误中。
这种模式之所以特别方便,其中一个原因是由于 Objective-C 中的 nil messaging,可以安全地进行,而不会产生任何影响。我不会在这里详细介绍为什么这很有用,但你可以在互联网上其他地方阅读更多相关内容(只需确保找到最新的文章;以前发送给 nil 的方法返回浮点值时,可能不会返回 0,但现在它们会,如文档所述)。

非常详细的答案,非常感谢!很抱歉延迟了接受它——必须先学习并尝试所有内容。 - Max Yankov

6
在Objective-C中,应尽可能少地使用异常。在其他语言使用异常时,在Objective-C中建议大多数情况下使用NSError对象。
关于异常处理,苹果的文档在这里:http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/Exceptions/Exceptions.html%23//apple_ref/doc/uid/10000012il 那么如何使用NSError对象呢?如果我们看一下苹果的类,返回错误会使用一个间接指针。
例如:
- (NSObject *)objectFromSet:(NSSet *)set error:(NSError **)error 
{
    // get an object from a set; if the set has at least 1 object 
    // we return an object, otherwise an error is returned.

    NSObject *object = [set anyObject]
    if (!object) 
    {
         *error = [NSError errorWithDomain:@"AppDomain" code:1000 userInfo:nil];
         return nil;
    }

    return object;
}

// and then we use the function like this
- (void)test
{
    NSError *error = nil;
    NSSet *set = [[[NSSet alloc] init] autorelease];
    NSObject *object = [self objectFromSet:set error:&error];
    if (object) 
    {
        // use the object, all went fine ...
    }
    else 
    {
        // handle error, perhaps show an alert view ...
    }
}

5
从概念上来说是正确的,但代码写得非常不好。首先,在objectFromSet:error:方法中进行赋值之前,需要检查error是否为非空(non-NULL)。其次,您不应该通过测试错误来检查错误。必须检查返回值,仅当返回nil时才考虑错误的内容。(此外,在调用者中初始化errornil没有要求。) - bbum
2
关于测试 NSError 输出参数的实际问题,可以参考这个答案:https://dev59.com/bHI95IYBdhLWcg3w-DH0#2511161(我很惊讶这个问题的被接受的答案是错误的) - ohhorob
@bbum:好吧,我没有意识到首先不应该检查错误,而是应该检查对象是否为nil,但这是有道理的(当查看返回错误的方法内部程序流程时;在发生错误的情况下,它将始终将返回值设置为nil)。所以我想即使像我这样有经验的iOS开发人员也可以偶尔学到新东西 :) - Wolfgang Schreurs

0

Objective-C支持异常处理,其语法与Java或C++类似。与NSError一样,Cocoa和Cocoa Touch中的异常也是对象,由NSException类的实例表示。

您可以使用

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

了解更多关于错误处理的内容苹果文档


0
如果一个方法应该返回一个对象,但无法这样做,它应该返回nil。如果有错误需要报告给用户以便他们采取某种行动,可以使用NSError对象。

为了完成这个答案:异常仅用于捕获不可恢复的错误。请阅读“异常编程主题”中的块引用:http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/Exceptions/Exceptions.html - Jano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接