在Objective-C中检查方法参数的最佳方法是哪种?

在编写方法或函数时,检查输入参数以响应任何可能的失败方案是一种很好的做法。

例如:

-(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString forKey:@"Name"]; } 

这看起来不错,但如果nameStringnil ,则应用程序将崩溃。 那么,我们可以检查它是否nil ,对吧? 我们还可以检查它是否是NSString而不是NSNumber,或者它是否响应我们的方法需要调用的方法。

所以我的问题是:检查这些论点的最完整和最优雅的方法是什么?

有多种方法可以实现这种保护:

  • NSParameterAssert
  • __attribute__((nonnull))
  • 和一个简单的if测试。

[编辑]自最新的Xcode版本(Xcode 6)以来,Apple增加了可注释性注释 ,这是另一种 – 更好 – 表达参数是否nil / null 。 您应该迁移到该表示法而不是使用__attribute__((nonnull))来使您的API更具可读性。


详情如下:

  1. NSParameterAssert是Apple的专用宏,用于检查方法参数的条件,如果失败则抛出专用exception。

    • 仅在运行时提供保护
    • 这个宏仍然认为传递nil作为参数是一个编程/概念错误 ,考虑到由于你的应用程序工作流程它通常不应该发生(由于其他条件确保param永远不会是nil ),如果它不是真的出错。
    • 它仍然会产生一个exception(你的调用代码可能会在必要时@try/@catch ),但它的优点是抛出的exception更明确(告诉参数预期不会是nil而不是与丑陋的崩溃)难以理解的callstack / message)。
  2. 如果你想让代码用nil调用你的方法但在这种情况下什么也不做,你可以简单地在函数/方法的开头if (!param) return

    • 这认为传递nil不是编程/概念错误,因此有时可能由于应用程序的工作流程而发生,因此这是一个可以发生并且不应该崩溃的可接受的情况。
  3. 不太常见的是 , 有__attribute__((nonnull)) GCC / LLVM属性 , 专用于告诉编译器函数/方法的某些参数应该不为null 。 这样,如果编译器在编译时可以检测到您尝试使用nil / NULL参数调用方法/函数(比如直接调用insertNameInDitionary:nil而不是使用在编译时无法确定该值的变量) ,它会立即发出编译错误,让你尽快修复它。

  4. [编辑]从最新的Xcode 6开始,您可以(并且应该)使用可空性注释而不是__attribute__((nonnull))__attribute__((nonnull)) 。 请参阅Apple博客文章中的示例。


总结如下:

如果要标记您的方法EXPECT您的参数为非零 ,从而表明使用nil调用它是一个逻辑错误,您应该执行以下操作:

 - (void)insertNameInDictionary:(NSString*)nameString __attribute__((nonnull)) { // __attribute__((nonnull)) allows to check obvious cases (directly passing nil) at compile time NSParameterAssert(nameString); // NSParameterAssert allows to check other cases (passing a variable that may or may not be nil) at runtime [myDictionary setObject:nameString forKey:@"Name"]; } 

如果你认为nil调用你的方法可以发生并且是可以接受的 ,并且你只是想在这些情况下避免崩溃,那么只需执行以下操作:

 -(BOOL)insertNameInDictionary:(NSString*)nameString { if (nameString == nil) return NO; [myDictionary setObject:nameString forKey:@"Name"]; return YES; } 

如果您认为您应该能够在字典中插入nil对象 ,则可以在该特定情况下将nil值转换为NSNull ,以便插入专用于此类用法的NSNull单例(我使用的是使用?:三元运算符使代码更紧凑):

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString?:[NSNull null] forKey:@"Name"]; } 

最后一种情况,如果你想在那个特定的例子中传递nil只是从myDictionary 删除 Name ,你可以简单地做一个简单的if测试并调用removeObjectForKey:@"Name"如果nameStringnil并调用setObject:forKey: if if’s不…或者你可以使用KVC和genericssetValue:forKey: KVC方法,根本不特定于NSDictionary,所以不要与setObject:forKey:混淆,在NSDictionary的情况下确实具有相同的行为(删除如果我们为值参数传递nil ,则从字典中键入。

Apple建议使用NSAssert

 NSAssert(nameString, @"nil nameString is not allowed"); 

此断言将终止您的程序,并生成一条错误消息,说明发生了什么。

上面, != nil部分是隐式的,因为Objective-C允许它。 您可以明确说明它以获得更好的可读性:

 NSAssert(nameString != nil, @"nil nameString is not allowed"); 

断言不仅限于nil检查,因为它们可以采取任意复杂的条件。 您可以检查参数是否为预期类型,它是否响应特定选择器,依此类推。 可以在发布代码中禁用断言以节省CPU周期和电池。

 - (void)insertNameInDictionary:(NSString *)nameString { if ([nameString isKindOfClass:[NSString class]]) { [myDictionary setObject:nameString forKey:@"Name"]; } } 

我在这看到几个问题。 NSMutableDictionary没有“ setObject ”方法。 您需要指定一个键。

 -(void)insertNameInDictionary:(NSString*)nameString { if(nameString) [myDictionary setObject:nameString forKey: @"someKey"]; } 

你也可以通过我的例子中的“ if(nameString) ”位进行nil检查。 如果你想要一个空字符串,请使用@""而不是nil。

这一切都取决于你想要发生什么的语义,如果你想要删除KV对,如果值为nil,那么你可以使用setValue:forKey:

喜欢:

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setValue:nameString forKey:@"Name"]; } 

或者如果要存储表示nil值的值,可以使用NSNull

 -(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString? :[NSNull null] forKey:@"Name"]; } -(NSString *)nameInDictionary { NSString * retVal = [myDictionary objectForKey:@"Name"]; return (retVal==[NSNull null]) ? nil : retVal; } 

或者您可能只想记录它。

 -(void)insertNameInDictionary:(NSString*)nameString { if(!nameString) { NSLog(@"expected nameString to be not null... silly me %s",__PRETTY_FUNCTION__); return; } [myDictionary setObject:nameString forKey:@"Name"]; }