4

在嵌套字典和数组中防御 nil 值的最有效方法是什么?

我有一个包含字典的数组,其中包含数组,其中包含字典,所以我有一个想要返回如下内容的方法:

return myArray[0][@"Categories"][0][@"Content"];

目前我有以下内容,但我想知道是否有更清洁或更优雅的方式来保护这些嵌套结构:

if (myArray) {
  // Add a check for myArray.count > 0
  if ([myArray[0] objectForKey:@"Categories"]) {
    // Add a check for myArray[@"Categories"].count > 0
    if ([myArray[0][@"Categories"][0] objectForKey:@"Content"]) {
      return myArray[0][@"Categories"][0][@"Content"];
    }
  } 
}

有一个更好的方法吗?有没有我没堵住的洞?提前致谢!

编辑:我添加了添加检查数组计数的注释。通过这些添加,我对 myArray[0][@"Categories"][0][@"Content"] 对象的访问安全吗?

4

6 回答 6

4

数组或字典都不能包含nil. 因此,您正在尝试防止请求的键/索引不存在。

如果字典键不存在那很好,您将nil返回并且任何消息都nil被“忽略”。

当您有一个数组并且您尝试请求一个超出范围的索引时,就会出现问题。所以你真正应该做的是检查count数组而不是检查键的存在。

于 2013-07-15T14:15:55.940 回答
2

正如其他人所说,NSDictionary 和 NSArray 都不能包含nil值。但是,它们可以包含(单例)值,如果找不到键,则NSNullNSDictionary 将从中返回nilobjectForKey

在“防御性”引用方面,特别是对于解码的 JSON,您可能需要进行多次检查。

首先,确保您确实拥有预期的对象类型(一些 JSON 提供程序可能会在一次访问时返回一个外部数组,在下一次访问时返回一个外部字典),方法是使用isKindOfClassif ([iThinkItsADict isKindOfClass:(NSDictionary class)]) { ...

接下来,如果访问数组,请确保您要使用的索引在数组的范围内(请记住,数组可能有零项): if (arrayIndex < someArray.count) { ...

从字典返回值时nil,如果未找到键,则可能是 。所以你可以测试if (returnedValue != nil) {... (虽然请注意,如果一个 nil 值用于“分派”一个方法调用,该调用将“成功”没有错误但返回零/nil。所以并不总是需要检查 nil。)

字典或数组可以返回一个 NSNull,您可以使用它进行检查if (returnedValue != [NSNull null]) {... (有些会反对您不应该使用的对象==!=比较对象是否相等,但应用程序中只有一个NSNull对象。)

于 2013-07-15T14:58:09.753 回答
1

NSArrays 和s都NSDictionary不能有nil值。如果您尝试使用一个或插入一个来创建它们,它们会引发异常。

于 2013-07-15T14:15:47.850 回答
0

我认为使用自省就足够了,检查你得到的对象是数组还是字典,记住 nil 目标动作被忽略,它们返回零:

if([myArray isKindOfClass: [NSArray class]] && myArray.count)
{ // If myArray is nil isKindOfClass: and count will return 0
    if([myArray[0] isKindOfClass: [NSDictionary class]])
    {
        if([myArray[0][@"Categories"] isKindOfClass: [NSArray class]] && 
            [myArray[0][@"Categories"].count)
        {  // If it doesn't contain this key, objectForKey: returns nil 
           // and nil targeted actions are ignored.
            if([myArray[0][@"Categories"][0] isKindOfClass: [NSDictionary class]])
            {
                return myArray[0][@"Categories"][0][@"Content"];
                // If the key is not contained in the dictionary it will return nil
            }
        }
    }
}
于 2013-07-15T14:33:00.990 回答
0

这些是我用来防御 JSON 解析意外的类别方法,在这种情况下你没有得到规范会引导你期望的数组/字典。

@implementation NSObject (TWXNSObject)

- (BOOL)exists
{
   if ([[NSNull null] isEqual:self])
      return NO;
   return YES;
}

// an empty JSON dictionary comes through as an array
- (BOOL)isDictionary
{
   if (!self.exists)
      return NO;
   if (![self isKindOfClass:[NSDictionary class]])
      return NO;
   return YES;
}

@end

验证您的数组访问的方法如下所示:

- (BOOL)isIndexValid:(NSUInteger)idx
{
   if (!self.exists)
      return NO;
   if (![self isKindOfClass:[NSArray class]])
      return NO;
   if (idx >= self.count)
      return NO;
   return YES;
}

理论上,您可以调出 -objectForKeyedSubscript 和 -objectAtIndexedSubscript 的实现,以将这些检查透明地添加到下标语法中。在实践中,由于 NSDictionary 和 NSArray 是类集群,因此可以预见它们迟早会以令人惊讶的方式失败。所以你最好定义类似的东西

@implementation NSDictionary (TWXNSDictionary)

- (id)safeObjectForKey:(id)key
{
   if (self.isDictionary)
      return [self objectForKey:key];
   return nil;
}

@end

@implementation NSArray (TWXNSArray)

- (id)safeObjectAtIndex:(NSUInteger)idx
{
   if ([self isIndexValid:idx])
      return [self objectAtIndex:idx];
   return nil;
}

@end

并在您访问来自 JSON 解析或其他可能与您的期望不匹配的来源的数据结构时使用它们。

于 2013-07-16T13:56:06.553 回答
0

我认为最好的方法是调酒objectAtIndexedSubscript

像这样Objective-C Literals: Negative Array Subscripts

method_exchangeImplementations(
    class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:)),
    class_getInstanceMethod(self, @selector(BLC_objectAtIndexedSubscript:))
);

- (id)BLC_objectAtIndexedSubscript:(NSInteger)idx {
    if (idx < 0) {
        idx = [self count] + idx;
    }

    return [self BLC_objectAtIndexedSubscript:idx];
}
于 2014-08-13T04:09:37.417 回答