不返回可变对象会有什么原因吗?

7

我有许多与以下函数类似的功能:

+ (NSArray *)arrayOfSomething
{
    NSMutableArray *array = [NSMutableArray array];

    // Add objects to the array

    return [[array copy] autorelease];
}

我的问题是关于这个方法的最后一行:是返回可变对象并避免复制操作更好,还是返回不可变副本?有没有好的理由避免在不期望的情况下返回可变对象?

(我知道返回NSMutableArray是合法的,因为它是NSArray的子类。我的问题是这是否是一个好主意。)

注:mutable object指可变对象,immutable copy指不可变副本。

当然,你举的例子可以直接返回原始值而不必先创建一个副本。另外,考虑到这是一个类方法(顺便说一下,并不是一个函数),你真的需要每次都创建一个新数组的实例吗?如果缓存了副本,那么这个问题基本上就没有意义了。 - jlehr
+1,因为在寻找答案时,我学到了所有Objective-C程序员都应该知道的非常重要的东西。 - JeremyP
2个回答

4
这是一个复杂的话题。我认为最好是参考苹果关于对象可变性的指南。
苹果在使用内省来确定返回对象的可变性方面有如下说法:
“要确定是否可以更改接收到的对象,接收方必须依赖返回值的形式类型。例如,如果它接收到一个被定义为不可变的数组对象,它就不应该尝试对其进行变异。基于类成员身份来确定对象是否可变是不可接受的编程实践。”(我强调了)
文章继续给出了几个非常好的理由,说明为什么不应该使用内省来确定是否可以突变返回的对象,例如:
“你从文件中读取属性列表。当Foundation框架处理该列表时,它注意到各种子集都是相同的,因此它创建了一组对象,并在所有这些子集之间共享。之后,您查看创建的属性列表对象并决定突变其中一个子集。突然之间,而且没有意识到,您已经在多个位置更改了树。”

你可以向 NSView 请求它的子视图(使用 subviews 方法),它会返回一个被声明为 NSArray 的对象,但实际上可能在内部是一个 NSMutableArray。然后你将该数组传递给其他代码,通过内省检测其可变性并对其进行更改。通过更改此数组,该代码正在改变 NSView 的内部数据结构。
基于以上情况,在你的示例中返回可变数组是完全可以接受的(当然,前提是在返回后不再自己对其进行更改,否则就会违反合同)。
话虽如此,几乎没有人阅读 Cocoa Objects Guide 的这一部分,所以防御性编程应该要求你制作一个不可变副本并返回它,除非性能分析表明这样做会有问题。

1
哎呀,我已经多次参考了那份文档,但总是匆匆忽略了那一部分。我正是你想要防范的那种程序员。(为自己辩护一下,我从来没有测试过对象的可变性。) - Kris Markel
1
@Robot K:加入这个俱乐部吧。正如我在回复kubi的答案时所说的第一条评论所示,如果我没有决定为了这个问题研究这个主题,我永远不会质疑在返回的不可变对象上使用内省的合法性。就像你一样,我从未尝试过这样的事情。 - JeremyP
请参阅NSObject isKindOfClass:方法的文档以获取类似的警告:http://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/isKindOfClass: - titaniumdecoy

3
简短回答:不要这样做。
长篇回答:这取决于情况。如果数组在被使用的过程中发生了改变,而使用者期望它是静态的,那么你可能会引起一些令人困惑的错误,这将会很难追踪。最好像你所做的那样进行复制/自动释放,并且只有在发现存在显著的性能问题时才会重新审视该方法的返回类型。
针对评论,我认为返回可变数组不太可能引起任何麻烦,但是,如果确实出现了问题,追踪问题可能会很困难。如果复制可变数组会导致性能下降,那么很容易确定问题的根源。你可以在两个非常不可能的问题之间进行选择,一个问题很容易解决,另一个问题则非常困难。

你能否举一个现实生活中会发生奇怪事情的例子?在我看来,很难在不引起编译器每次“正常”方法调用时都发出警告的情况下悄悄地干扰数组。当然,除了performSelector和其他类似方法之外...? - w-m
1
在某个地方,一些代码可能会测试类是否可变并开始修改它。这对于期望不可变数组的其他类来说将是有问题的。虽然这不太可能发生,但确实可能发生。 - Kris Markel
此外,一些类(特别是框架类)会检查它们是否被传递了可变数组,然后复制它。返回一个不可变数组将跳过这一步骤。 - Kris Markel
@RobotK。有没有办法检查NSArray子类的可变性(而不使用私有API或其他技巧)? - jlehr
@Robot K:NSMutableArray和NSArray是类簇的一部分。根据情况,您可能会得到不同类型的对象,有时候,差异仅在对象本身的标志中告诉您它是否可变。更好的方法是查看它是否响应您想要使用的变异方法。 - JeremyP
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接