用于访问数组和字典的防御措施

时间:2013-07-15 14:12:26

标签: ios objective-c nsarray nsdictionary

在嵌套字典和数组中防御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”]中的对象安全吗?

6 个答案:

答案 0 :(得分:4)

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

如果字典键不存在,那么你只需返回nil,任何发送给nil的邮件都会被“忽略”。

当你有一个数组并且你试图要求一个超出范围的索引时,就会出现问题。所以你应该做的是检查数组的count,而不是检查密钥是否存在。

答案 1 :(得分:2)

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

就“防御性”引用而言,尤其是解码后的JSON,您可能需要进行多次检查。

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

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

如果从字典返回值,则可能是nil,如果找不到该键。因此,您可以使用if (returnedValue != nil) {...进行测试(尽管请注意,如果使用nil值来“调度”方法调用,则调用将“成功”而不会出现错误但返回零/零。因此,并不总是需要检查为零。)

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

答案 2 :(得分:1)

NSArrayNSDictionary都不能有nil个值。如果您尝试使用一个或插入一个,则会抛出异常。

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

答案 4 :(得分: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解析的数据结构或可能与您的期望不匹配的其他来源时使用它们。

答案 5 :(得分: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];
}