我正在查看用于反序列化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];
}
答案 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; (id
或NSArray *
) - 当稍后提取该实例时,对某些的知之甚少,但如果程序员只存储了,例如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
的价值是多少? 玛丽·威廉姆斯 ......可能不是故意的。
如果您可以创建自己的字符串,可能会产生什么结果,您可以从声称返回不可变字符串并将其分配给maidenName
或marriedName
的方法获取它,但实际上返回字符串是可变的,然后在其他地方更改?可怜的玛丽会发现她的名字正在改变。
为了解决这个问题,通常建议采用两条规则:
Consumer:如果要存储对具有可变子类的不可变类的对象的引用,则在存储之前复制该对象,以避免在实例存在时出现意外情况实际上是可变的。对于上面的示例,可以通过将copy
属性添加到属性来完成此操作:
@property (copy) NSString *maidenName;
@property (copy) NSString *marriedName;
生产者:如果您正在创建并返回一个您声明为不可变的对象实例,但在创建过程中您使用了一个可变子类,然后创建一个不可变副本并返回该实例。即返回你说你要返回的内容(或者它的不可变子类)。
遵循这些规则可以减少意外,这就是responseObjectForResponse
的代码所做的。
你是对的,这是防御性编程。然而;由于类型信息丢失的普遍存在,这种编程风格的基础,以及意外可变性可能导致的问题;它不是高度防御性的编程。
正如Monty Python和其他人所建议的那样:总是期待出乎意料的并且防御它。 HTH