Ios 为什么此代码示例在返回之前复制?

Ios 为什么此代码示例在返回之前复制?,ios,objective-c,Ios,Objective C,我正在看一个反序列化JSON响应的代码示例。最后一行返回[topics copy]在返回之前复制数组。我已经查找了原因,它返回一个不变的NSArray 然而,这是标准做法还是高度防御性的编程?调用方法会将返回值分配给某个对象,如果它想将返回值分配给不可变的NSArray,它就会这样做。如果它将返回值分配给NSMutableArray,则它将执行此操作 所以我的问题是——有没有任何现实的场景可以防止不必要的后果 // Returns array of @c NPTopic objects - (i

我正在看一个反序列化JSON响应的代码示例。最后一行
返回[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];
}
//返回@c NPTopic对象的数组
-(id)responseObjectForResponse:(NSURResponse*)响应数据:(NSData*)数据错误:(NSError*)自动删除*)错误
{
if(![self validateResponse:(NSHTTPPURLResponse*)响应数据:数据错误:错误]){
返回零;
}
NSDictionary*JSONDictionary=[超级响应对象ForResponse:响应数据:数据错误:错误];
如果(!JSONDictionary)返回nil;
//注意:此响应的预期JSON格式为{data:[{},{}],元数据:{…}
NSArray*JSONTopics=JSONDictionary[@“数据”];
NSMutableArray*主题=[NSMutableArray];
用于(NSDictionary*JSONTopics中的JSONTopic){
//对于JSON格式的每个主题,我们可以使用Mantle将其从JSON反序列化到所需的模型类
NPTopic*topic=[MTLJSONAdapter modelOfClass:[NPTopic class]fromJSONDictionary:JSONTopic error:error];
如果(!topic)返回nil;
[主题添加对象:主题];
}
*误差=零;
返回[主题副本];
}

副本是这样的,它返回一个
NSArray
,而不是
NSMutableAray
。问题是,如果返回一个
NSMutableAray
,它可能会被更改,这可能是一个问题,因为有多个指向它的指针,一个指针会进行更改,而另一个指针则假定它是不可变的,不会更改

这是很好的做法

不要对实际实现进行假设,有几种方式可以实现“复制”,而不需要实际制作副本。在没有需要和证据的情况下关注性能被称为“过早优化”,许多人再次警告,包括著名的Donald Knuth


它确实应该将其返回类型设置为
NSArray*
,而不是
id
,这样编译器就可以捕获类型错误。

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

Objective-C和许多其他语言的基础是子类的概念;当需要类
a
的实例时,通常可以使用从类
a
派生的类
B
的实例

因此存在着大量的类型信息丢失;将
B
的实例传递给期望
A
的对象,则接收方不知道,除非它选择查询实际类型-接收方关于实例实际类型的信息较少,将其作为
A
,而实际实例仍然是A
B

类型信息丢失的极端情况是当实例存储在容器中时,例如
NSArray
,该容器只存储“对象”(
id
NSArray*
)——当该实例稍后被提取时,对它的了解肯定很少,尽管程序员只存储了,
NSString
实例,则它们可以安全地假定只提取了
NSString
实例

所有这些通常都很有效

当派生类中的一些基本属性(如易变性)发生变化时,“通常”发生故障

考虑简单类(请不要有性别歧视等评论!):

以及代码片段:

Woman *mary = [Woman new];

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

[name replaceOccurencesOfString:@"Jones" with:@"Williams];
mary.marriedName = name;
mary.maidenName
的值是多少?玛丽·威廉姆斯。。。可能不是我们想要的

如果你能像上面那样创建你自己的字符串,结果可能是什么?你从一个声称返回一个不可变字符串的方法获得它,并将它分配给
maidenName
marriedName
,但实际上返回字符串是可变的,随后在其他地方更改了它?可怜的玛丽会发现她的名字在变

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

消费者:如果您正在存储对具有可变子类的不可变类的对象的引用,则在存储之前复制该对象,以避免在实例实际上是可变的情况下发生意外。对于上述示例,可以通过将
copy
属性添加到属性中来实现:

@property (copy) NSString *maidenName;
@property (copy) NSString *marriedName;
生产者:如果您正在创建并返回一个声明为不可变的对象实例,但在创建过程中使用了一个可变的子类,那么创建一个不可变的副本并返回该实例。也就是说,返回您要返回的内容(或它的不可变子类)

遵循这些规则应该可以减少意外,而
responseObjectForResponse
的代码就是这样做的

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

正如Monty Python和其他人所建议的那样:总是预料到意外情况并加以防范。
HTH

旁注:如果您不是在ARC环境中运行,您应该将最后一行更改为使用
[[topics copy]autorelease]
。明白了。但假设它确实传回了
NSMutableArray
。我的问题是,由于调用方法将在返回时立即将此返回值分配给某个对象,这是否意味着不存在在不应该使用NSMutableArray的情况下使用NSMutableArray的情况?我的意思是,我猜如果调用方法将返回值分配给type
id
,那么
@property (copy) NSString *maidenName;
@property (copy) NSString *marriedName;