为什么这个代码样本在返回之前复制?

时间:2015-02-06 17:31:50

标签: ios objective-c

我正在查看用于反序列化JSON响应的代码示例。最后一行return [topics copy];在返回之前复制数组。我已经找到了原因,并且返回了一个不可变的NSArray。

然而,这是标准练习还是高防御性编程?调用方法会将返回值赋给某个​​东西,如果它想要将返回值赋给不可变NSArray,它就会这样做。如果它将返回值分配给NSMutableArray,那么它就会这样做。

所以我的问题是 - 是否有任何现实的情况可以防止不必要的后果?

// Returns array of @c NPTopic objects
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error
{
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        return nil;
    }

    NSDictionary *JSONDictionary = [super responseObjectForResponse:response data:data error:error];
    if (!JSONDictionary) return nil;

    // Note: the expected JSON format of this response is { data: [ { <a topic> }, { <another topic>} ], metadata: { ...} }
    NSArray *JSONTopics = JSONDictionary[@"data"];

    NSMutableArray *topics = [NSMutableArray array];
    for (NSDictionary *JSONTopic in JSONTopics) {
        // For each topic in JSON format, we can deserialize it from JSON to our desired model class using Mantle
        NPTopic *topic = [MTLJSONAdapter modelOfClass:[NPTopic class] fromJSONDictionary:JSONTopic error:error];
        if (!topic) return nil;
        [topics addObject:topic];
    }

    *error = nil;
    return [topics copy];
}

2 个答案:

答案 0 :(得分:1)

副本是这样的,它会返回NSArray,而不是NSMutableAray。问题是如果返回NSMutableAray它可以被更改,这可能是一个问题,有多个指针,一个进行更改,但另一个假设它是不可变的,不会发生变化。< / p>

这是一种很好的做法。

不要对实际实施做出假设,有几种方法可以复制&#34;&#34;复制&#34;可以在没有实际复制的情况下发生。关注性能而不需要和证明被称为&#34;过早优化&#34;许多人再次警告,包括着名的唐纳德克努特。

它确实应该返回键入NSArray *,而不是id,因此编译器可以捕获类型错误。

答案 1 :(得分:1)

根据您对@Zaph的评论,让我们尝试解释一下......

Objective-C和许多其他语言的基础是子类的概念;类B的实例派生自类A,只要需要类A的实例,就可以使用

因此有很多类型信息丢失;将B的实例传递给期待A的内容,然后接收方不知道,除非它选择查询实际类型 - 接收方的信息较少实例的实际类型,将其视为A,而实际实例仍为B

类型信息丢失的极端情况是将实例存储在容器中,例如NSArray,这只是存储&#34;对象&#34; (idNSArray *) - 当稍后提取该实例时,对某些知之甚少,但如果程序员只存储了,例如NSString实例然后他们可以安全假设仅提取NSString个实例。

所有这些通常都能正常工作。

&#34;通常&#34;分解是指某些基本属性(如可变性)在派生类中发生变化。

考虑简单的课程(请不要发表性别歧视等评论!):

@interface Woman : Person

@property NSString *maidenName;
@property NSString *marriedName;

@end

@implementation Woman
// nothing to do
@end

和代码片段:

Woman *mary = [Woman new];

NSMutableString *name = [NSMutableString stringWithString:@"Mary Jones"];
mary.maidenName = name;

[name replaceOccurencesOfString:@"Jones" with:@"Williams];
mary.marriedName = name;

mary.maidenName的价值是多少? 玛丽·威廉姆斯 ......可能不是故意的。

如果您可以创建自己的字符串,可能会产生什么结果,您可以从声称返回不可变字符串并将其分配给maidenNamemarriedName的方法获取它,但实际上返回字符串是可变的,然后在其他地方更改?可怜的玛丽会发现她的名字正在改变。

为了解决这个问题,通常建议采用两条规则:

Consumer:如果要存储对具有可变子类的不可变类的对象的引用,则在存储之前复制该对象,以避免在实例存在时出现意外情况实际上是可变的。对于上面的示例,可以通过将copy属性添加到属性来完成此操作:

@property (copy) NSString *maidenName;
@property (copy) NSString *marriedName;

生产者:如果您正在创建并返回一个您声明为不可变的对象实例,但在创建过程中您使用了一个可变子类,然后创建一个不可变副本并返回该实例。即返回你说你要返回的内容(或者它的不可变子类)。

遵循这些规则可以减少意外,这就是responseObjectForResponse的代码所做的。

你是对的,这是防御性编程。然而;由于类型信息丢失的普遍存在,这种编程风格的基础,以及意外可变性可能导致的问题;它不是高度防御性的编程

正如Monty Python和其他人所建议的那样:总是期待出乎意料的并且防御它。 HTH