在Objective-C中检查方法参数的最佳方法是哪种?
在编写方法或函数时,检查输入参数以响应任何可能的失败方案是一种很好的做法。
例如:
-(void)insertNameInDictionary:(NSString*)nameString { [myDictionary setObject:nameString forKey:@"Name"]; }
这看起来不错,但如果nameString
为nil
,则应用程序将崩溃。 那么,我们可以检查它是否nil
,对吧? 我们还可以检查它是否是NSString而不是NSNumber,或者它是否响应我们的方法需要调用的方法。
所以我的问题是:检查这些论点的最完整和最优雅的方法是什么?
有多种方法可以实现这种保护:
-
NSParameterAssert
-
__attribute__((nonnull))
- 和一个简单的
if
测试。
[编辑]自最新的Xcode版本(Xcode 6)以来,Apple增加了可注释性注释 ,这是另一种 – 更好 – 表达参数是否nil
/ null
。 您应该迁移到该表示法而不是使用__attribute__((nonnull))
来使您的API更具可读性。
详情如下:
-
NSParameterAssert
是Apple的专用宏,用于检查方法参数的条件,如果失败则抛出专用exception。- 这仅在运行时提供保护
- 这个宏仍然认为传递
nil
作为参数是一个编程/概念错误 ,考虑到由于你的应用程序工作流程它通常不应该发生(由于其他条件确保param永远不会是nil
),如果它不是真的出错。 - 它仍然会产生一个exception(你的调用代码可能会在必要时
@try/@catch
),但它的优点是抛出的exception更明确(告诉参数预期不会是nil
而不是与丑陋的崩溃)难以理解的callstack / message)。
-
如果你想让代码用
nil
调用你的方法但在这种情况下什么也不做,你可以简单地在函数/方法的开头if (!param) return
。- 这认为传递
nil
不是编程/概念错误,因此有时可能由于应用程序的工作流程而发生,因此这是一个可以发生并且不应该崩溃的可接受的情况。
- 这认为传递
-
不太常见的是 , 有
__attribute__((nonnull))
GCC / LLVM属性 , 专用于告诉编译器函数/方法的某些参数应该不为null 。 这样,如果编译器在编译时可以检测到您尝试使用nil
/NULL
参数调用方法/函数(比如直接调用insertNameInDitionary:nil
而不是使用在编译时无法确定该值的变量) ,它会立即发出编译错误,让你尽快修复它。 -
[编辑]从最新的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"
如果nameString
是nil
并调用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"]; }