【问题标题】:When does -copy return a mutable object?-copy 什么时候返回一个可变对象?
【发布时间】:2011-08-27 02:56:06
【问题描述】:

我在Cocoa and Objective C: Up and Running 中读到-copy 将始终返回一个不可变对象,-mutableCopy 将始终返回一个可变对象:

重要的是要知道在可变对象上调用 -copy 会返回一个不可变对象 版本。如果你想复制一个可变对象并在新版本中保持可变性, 您必须在原件上致电-mutableCopy。不过,这很有用,因为如果你想 要“冻结”一个可变对象,您只需调用 -copy 即可。

所以我有这样的事情:

NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init];
NSLog( @"%@", [req className] );               // NSMutableURLRequest
NSLog( @"%@", [[req copy] className] );        // NSMutableURLRequest
NSLog( @"%@", [[req mutableCopy] className] ); // NSMutableURLRequest

据此previous answer

你不能依赖复制的结果是可变的!复制NSMutableArray 可能 返回一个NSMutableArray,因为那是原始类,但复制任意 NSArray 实例不会。

这似乎与NSURLRequest 有点隔离,因为NSArray 的行为符合预期:

NSArray *arr = [[NSMutableArray alloc] init];
NSLog( @"%@", [arr className] );                 // __NSArrayM
NSLog( @"%@", [[arr copy] className] );          // __NSAraryI
NSLog( @"%@", [[array mutableCopy] className] ); // __NSArrayM

所以...

  1. -copy 何时返回不可变对象(如预期)以及何时返回可变对象?
  2. 如何实现获取拒绝“冻结”的可变对象的“冻结”副本的预期效果?

【问题讨论】:

  • 我认为您在帖子中得到了答案。
  • @Oscar A is true and B is false 并不能完全证明判断 C 是否为真的规则。

标签: objective-c cocoa mutable nsmutableurlrequest


【解决方案1】:

我认为您已经发现了文档与现实之间的巨大裂痕。

NSCopying 协议文档声称:

如果“不可变与可变”的考虑适用于接收对象,则返回的副本是不可变的;否则副本的确切性质由类确定。

但这在某些情况下显然是错误的,正如您在示例中所展示的那样(我已通过该文档页面向他们发送了有关此问题的反馈)。

但是(#2)在我看来,这实际上并不重要,你不应该在意。

-copy 的意义在于,它将返回一个您可以使用的对象并保证它的行为独立于原始对象。 这意味着如果您有一个可变对象,-copy它,并且改变原始对象,副本将看不到效果。 (在某些情况下,我认为这意味着 -copy 可以优化为什么都不做,因为如果对象是不可变的,它一开始就无法更改。我可能错了。(我现在想知道因此,字典键的含义是什么,但这是一个单独的主题...))

正如您所见,在某些情况下,新对象实际上可能属于可变类(即使文档告诉我们它不会)。但只要你不依赖它是可变的(你为什么会这样?),那没关系。

你应该怎么做? 始终将-copy 的结果视为不可变,就这么简单。

【讨论】:

  • 同意。但是,这并没有解决防御性编程用例:如果您想在访问器中公开不可变对象怎么办?在这种情况下,您确实关心copy 返回的内容。只一次制作一个不可变的副本并一遍又一遍地返回它会很好 - 但是没有办法做到这一点,所以你必须制作一个副本每次访问器被调用,以防副本是可变的。
  • @PaulCantrell 当然。 IMO 过于防御;例如,如果您返回 NSArray,任何想要对其进行变异的人都必须首先将其转换为 NSMutableArray,我认为这是程序员错误(或“未定义行为”的情况)。注意 Swift 解决了这个问题,因为 Array 是一个值类型 :)
  • @jtbandes 好吧,防御性编程的正确程度始终取决于您要防御的内容!但是,是的,Swift 的结构是我见过的第一个解决可变性检查问题的解决方案,并且在实践中确实有效。
【解决方案2】:

1) -copy 什么时候返回一个不可变对象(如预期的那样),什么时候返回一个可变对象?

您应该始终将其视为不可变的变体。不应使用返回类型的可变接口。除了优化之外,答案应该无关紧要,除非记录在案,否则应将其视为实现细节。

显而易见的情况:由于多种原因,objc 类集群和类设计可能很复杂。返回可变副本可能只是为了方便。

2) 我如何实现获得拒绝“冻结”的可变对象的“冻结”副本的预期效果?

使用不可变类的复制构造函数是一个好方法(类似于 St3fan 的回答)。像copy,这不是保证。

我能想到的唯一原因是您为什么要强制执行此行为是为了性能或强制执行受限接口(除非它是学术性的)。如果您想要性能或受限制的接口,那么您可以简单地封装一个在创建时复制并仅公开不可变接口的类型的实例。然后您通过保留实现复制(如果这是您的意图)。

或者,您可以编写自己的子类并实现自己的副本变体。

最后的手段:许多 cocoa 可变/不可变类都是纯接口 - 如果需要确保特定行为,您可以编写自己的子类 - 但这很不寻常。

也许更好地描述为什么应该强制执行这将是好的 - 现有的实现对于绝大多数开发人员/使用来说都很好。

【讨论】:

    【解决方案3】:

    请记住,没有一个copy 实现——每个类都有自己的实现。而且,众所周知,Objective C 运行时的实现在某些地方有点“松散的糊涂”。所以我认为我们可以说大部分 copy 返回一个不可变的版本,但也存在一些例外。

    (顺便说一句,这是做什么的:

    NSArray *arr = [[NSMutable array] init];
    

    ?)

    【讨论】:

      【解决方案4】:

      将对象转换为可变对象的最佳方法是使用可变的“构造函数”。比如:

      NSArray* array = ...;
      NSMutableArray* mutableArray = [NSMutableArray arrayWithArray: array];
      

      复制用于制作对象的副本。不要改变它的可变性。

      【讨论】:

      • 如果它实现了NSMutableCopying,要么发送mutableCopy 消息,要么发送一个NSMutableCopying——两者都是获取可变副本的好方法。不过,这不是提问者要问的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-16
      • 1970-01-01
      • 1970-01-01
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多