方法中的错误/异常处理,该方法返回布尔值

10
在我的自定义框架中,我有一个类似下面的方法,它从字典中获取值并将其转换为BOOL类型,并返回布尔值。
- (BOOL)getBoolValueForKey:(NSString *)key;
如果该方法的调用者传递了一个不存在的键,那么我应该抛出一个自定义的NSException来表示该键不存在(但是在Objective C中不推荐抛出异常),还是像下面所示的添加NSError参数到该方法中?
- (BOOL)getBoolValueForKey:(NSString *)key error:(NSError **)error; 

如果我使用NSError,我将不得不返回“NO”,这会让人误解,因为“NO”可能是任何有效键的有效值。


1
如果你使用NSError,当然你会返回NO,并且你会通过NSError返回一些内容,基于这个你可以告诉用户是否出现了错误。如果返回YES,你将NSError设置为nil;如果返回NO并且键存在,你也将NSError设置为nil,如果键不存在,你会返回NO并发送有效的NSError对象。 - Teja Nandamuri
3
或者,您可以选择使用 NSNumber 封装器返回值(即返回 @YES@NO),如果没有找到则返回 nil。调用者随后可以检查该值是否为 @YES@NO。这可能不完全符合您的设计,但在 Objective-C 中,这是一种高效的可选 BOOL 实现方式(除了分配一个 BOOL *,但那太过浪费)。 - Itai Ferber
1
如果调用者将NSError参数传递为NULL会怎么样? - saikamesh
2
@saikamesh:如果在尝试获取BOOL值时出现错误,那么这将是一个编程错误。因此,如果获取BOOL失败,则断言error!= NULL并存储错误值。它会崩溃,相关的程序员会修复它。 - gnasher729
1
如果获取BOOL值失败,另一种方法是传递一个默认值,该默认值将在失败时返回。 - gnasher729
显示剩余2条评论
6个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
4

在IT技术方面,这个API的使用一直由NSUserDefaults来进行,应该是您设计API的起点:

- (BOOL)boolForKey:(NSString *)defaultName;
如果在用户默认设置中将boolean值与defaultName相关联,则返回该值。否则,返回NO。 除非您有充分的理由,否则应避免为从keystore获取bools创建不同的API。在大多数ObjC接口中,获取不存在的键会返回nil,并且在布尔上下文中,nil被解释为NO。 传统上,如果想区分NO和nil,则调用objectForKey检索NSNumber并检查nil。同样,这是许多Cocoa keystore的行为,不应轻易更改。 但是,可能存在违反此预期模式的强烈原因(在这种情况下,您应该在文档中仔细记录它,因为这很令人惊讶)。在这种情况下,有几种已经确立的模式。 首先,您可以考虑将未知键获取视为编程错误,并抛出异常,以期望程序很快因此崩溃。为此创建新类型的异常非常不寻常(也意外)。您应该引发NSInvalidArgumentException,该异常正好解决了此问题。 其次,您可以通过正确使用get方法来区分nil和NO。您的方法以get开头,但不应该如此。在Cocoa中,get表示“按引用返回”,您可以将其用作这种方式。例如:
- (BOOL)getBool:(BOOL *)value forKey:(NSString *)key {
    id result = self.values[key];
    if (result) {
        if (value) {
            // NOTE: This throws an exception if result exists, but does not respond to 
            // boolValue. That's intentional, but you could also check for that and return 
            // NO in that case instead.
            *value = [result boolValue]; 
        }
        return YES;
    }

    return NO;
}

这个函数需要传入一个指向bool类型的指针,并在数值可用时填充它,返回YES。如果数值不可用,则返回NO

在这里没有必要涉及NSError,这会增加复杂性而不提供任何价值。即使您考虑Swift桥接,我也不会在这里使用NSError来获取throws。相反,您应该编写一个简单的Swift包装器,该方法返回Bool?。 这是一种更强大且更容易在Swift端使用的方法。


1
我选择按照NSUserDefaults的方式来实现和记录该方法。 - saikamesh

3
如果您想在程序员出错时传递不存在的键,即在运行时期间实际上永远不应该发生的情况(例如上游应该处理该可能性),那么断言失败或NSException是实现此目的的方法。引用苹果文档中的话:异常编程指南: “你应该将异常保留给编程错误或意外运行时错误,比如越界访问集合、尝试改变不可变对象、发送无效消息和失去与窗口服务器的连接。通常,当创建应用程序时就会处理这些错误。” 如果您想传达可以从中恢复/继续执行的运行时错误,则添加错误指针是实现此目的的方法。 原则上,即使存在非关键错误情况,也可以将BOOL用作返回类型。但是,在使用Swift接口这段代码时,有一些角落案例需要注意: - 如果您通过Swift访问此API,则始终意味着抛出错误,即使在Objective-C方法实现中没有填充错误指针,您也需要使用do / catch并特别处理空错误。 - 相反的情况也是成立的,即成功的情况下也可能抛出错误(例如,NSXMLDocument通过这种方式传递非关键验证错误)。据我所知,没有办法将这些非关键错误信息传达给Swift。 - 如果您打算从Swift使用此API,则可以尝试将BOOL包装成可空的NSNumber(此时错误情况为nil,成功的NO情况为包装了NO的NSNumber)。 需要注意的是,特别针对可能会失败的setter,有强烈的惯例需要遵循。请参见其他答案中提到的内容。

1
你指出了苹果错误处理方法中的主要弱点。 我们通过确保在成功情况下NSError为空来处理这些情况,因此您可以实际检查错误:
if (error) { 
    // ... problem
    // handle error and/ or return        
}
由于这与苹果的错误处理相矛盾,其中Error不能保证为nil,但在失败情况下保证不为nil,因此受影响的方法必须得到很好的记录,以便客户端了解此特殊行为。这不是一个完美的解决方案,但是它是我所知道的最好的解决方案。(这是我们在Swift中不再需要处理的讨厌事之一)

0
在这种情况下,我更喜欢返回NSInteger而不是返回01NSNotFound,如果调用者传递了不存在的键。由于该方法的性质,处理NSNorFound应该是调用者的判断。正如我所看到的,从方法的名称来看,返回错误并不是对用户非常鼓励的做法。

0

如果您想要实现以下功能:

  1. 区分失败和成功的情况
  2. 仅在成功时使用布尔值
  3. 在失败的情况下,调用者错误地认为返回值是键的值

我建议采用基于块的实现。您将拥有一个successBlockerrorBlock以清晰地区分。

调用者将像这样调用该方法

[self getBoolValueForKey:@"key" withSuccessBlock:^(BOOL value) {
    [self workWithKeyValue:value];
} andFailureBlock:^(NSError *error) {
    NSLog(@"error: %@", error.localizedFailureReason);
}];

以及实现:

- (void)getBoolValueForKey:(NSString *)key withSuccessBlock:(void (^)(BOOL value))success andFailureBlock:(void (^)(NSError *error))failure {

    BOOL errorOccurred = ...

    if (errorOccurred) {
        // userInfo will change
        // if there are multiple failure conditions to distinguish between
        NSDictionary *userInfo = @{
                               NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was unsuccessful.", nil),
                               NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The operation timed out.", nil),
                               NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Have you tried turning it off and on again?", nil)
                               };

        NSError *error = [NSError errorWithDomain:@"domain" code:999 userInfo:userInfo];
        failure(error);

        return;
    }

    BOOL boolValue = ...
    success(boolValue);
}

0

我们使用这个

- (id) safeObjectForKey:(NSString*)key {
    id retVal = nil;

    if ([self objectForKey:key] != nil) {
        retVal = [self objectForKey:key];
    } else {
        ALog(@"*** Missing key exception prevented by safeObjectForKey");
    }

    return retVal;
}

头文件:NSDictionary+OurExtensions.h

#import <Foundation/Foundation.h>
@interface NSDictionary (OurExtensions)
- (id) safeObjectForKey:(NSString*)key;
@end

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