返回bool的方法中的错误/异常处理

时间:2016-10-18 15:05:36

标签: ios objective-c ios-frameworks nserror nsexception

在我的自定义框架中,我有一个如下所示的方法,它从字典中获取值并将其转换为BOOL并返回布尔值。

- (BOOL)getBoolValueForKey:(NSString *)key;

如果此方法的调用者传递了不存在的键,该怎么办?我应该抛出一个自定义的NSException,说密钥不存在(但是在目标c中不建议抛出异常)或者将NSError参数添加到此方法中,如下所示?

- (BOOL)getBoolValueForKey:(NSString *)key error:(NSError **)error; 

如果我使用NSError,我将不得不返回' NO'这将是误导,因为' NO'可以是任何有效密钥的有效值。

6 个答案:

答案 0 :(得分:4)

此问题的API由NSUserDefaults构建,应该是您设计API的起点:

- (BOOL)boolForKey:(NSString *)defaultName;
  

如果布尔值与用户默认值中的defaultName相关联,则返回该值。否则,返回NO。

除非你有充分的理由,否则你应该避免创建一个不同的API来从密钥库中获取bool。在大多数ObjC接口中,获取非exixtant键返回nil并且nil在布尔上下文中被解释为NO

传统上,如果想要区分NOnil,请致电objectForKey以检索NSNumber并检查nil。同样,这是许多Cocoa密钥库的行为,不应轻易改变。

然而,有可能有一个强烈的理由违反这种预期的模式(在这种情况下,你应该在文档中仔细注意,因为它是令人惊讶的)。在这种情况下,有几种完善的模式。

首先,您可以考虑将未知密钥作为编程错误提取,并且您应该抛出异常,期望程序很快就会崩溃。为此创建新的异常是非常不寻常的(并且是意外的)。你应该提出NSInvalidArgumentException,它确实存在于这个问题中。

其次,您可以通过正确使用nil方法来区分NOget。您的方法以get开头,但不应该。 get表示Cocoa中的“按引用返回”,您可以这样使用它。像这样:

- (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。相反,你应该围绕这个返回Bool?的方法编写一个简单的Swift包装器。这是一个更强大的方法,在Swift方面使用起来更简单。

答案 1 :(得分:3)

如果你希望传递一个不存在的密钥作为程序员错误,即在运行时实际上永远不会发生的事情,因为例如某些上游应该已经处理过这种可能性,那么断言失败或NSException就是这种方式去做吧。引用Exception Programming Guide中的Apple文档:

  

您应该保留使用编程异常或意外的运行时错误,例如越界集合访问,尝试改变不可变对象,发送无效消息以及丢失与窗口服务器的连接。在创建应用程序时而不是在运行时,通常会使用异常处理这些类型的错误。

如果您希望传达程序可以恢复/可以继续执行的运行时错误,那么添加错误指针就是这样做的。

原则上,即使存在非严重错误情况,也可以使用BOOL作为返回类型。但是,如果您打算使用Swift中的代码进行交互,则会出现这种情况:

  • 如果您通过Swift访问此API,NO总是暗示会抛出错误,即使您在Objective-C方法实现中没有填充错误指针,即您需要执行/捕获和处理特定的零错误。
  • 反向实际上也是有效的,即可能在成功案例中抛出错误(例如,NSXMLDocument会这样做以传达非关键验证错误)。据我所知,无法将此非关键错误信息传达给Swift。

如果你打算从Swift中使用这个API,我可能会把BOOL打包成一个可以为空的NSNumber(在这种情况下,错误情况将是nil,而成功的NO情况将是一个NSNumber,其中包含NO)

我应该注意,对于潜在可用的setter的特定情况,您应该遵循强大的约定as noted in one of the other answers

答案 2 :(得分:1)

您可以找出苹果错误处理方法的主要弱点。

我们通过在成功案例中保证NSErrornil来处理这些情况,因此您实际检查错误:

if (error) { 
    // ... problem
    // handle error and/ or return        
}

由于这与Apples错误句柄相矛盾,Error永远不能保证nil,但在失败的情况下保证不nil,受影响的方法必须有详细记录对客户了解这种特殊行为。

这不是一个好的解决方案,但我知道的最好。

(这是我们在swift中不再需要处理的令人讨厌的事情之一)

答案 3 :(得分:0)

如果你想要所有这些

  1. 区分失败和成功案例
  2. 仅在成功时使用bool值
  3. 如果失败,调用者错误地认为返回值不是键的值
  4. 我建议做一个基于块的实现。您将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);
    }
    

答案 4 :(得分: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

答案 5 :(得分:0)

在这种情况下,如果调用方传递不存在的密钥,我宁愿返回NSInteger并返回01NSNotFound。 从这种方法的本质来看,处理NSNorFound应该是来电者的判断。正如我所看到的,从方法名称返回错误对用户来说并不是很令人鼓舞。