如何将copy和mutableCopy应用于NSArray和NSMutableArray?

66

copymutableCopy在对NSArrayNSMutableArray进行操作时有什么区别?

以下是我的理解,是否正确?

// ** NSArray **
NSArray *myArray_imu = [NSArray  arrayWithObjects:@"abc", @"def", nil];

// No copy, increments retain count, result is immutable
NSArray *myArray_imuCopy = [myArray_imu copy];

// Copys object, result is mutable 
NSArray *myArray_imuMuta = [myArray_imu mutableCopy];

// Both must be released later

// ** NSMutableArray **
NSMutableArray *myArray_mut = [NSMutableArray arrayWithObjects:@"A", @"B", nil];

// Copys object, result is immutable
NSMutableArray *myArray_mutCopy = [myArray_mut copy];

// Copys object, result is mutable
NSMutableArray *myArray_mutMuta = [myArray_mut mutableCopy];

// Both must be released later

1
你在编辑中犯了一个错误,不确定是打字错误还是误解。在第一个代码块中,被赋值为mutableCopy的变量myArray_imuMuta是可变的,而不是你的注释所指示的不可变。 - Asher Dunn
谢谢,这是我的错误,我被Xcode的警告所困扰,它说“警告NSArray可能无法响应add -addObject / -removeObjectAtIndex。我会将myArray_imuMuta更改为NSMutableArray。” - fuzzygoat
@JamesHarnett - 请停止以“ARC兼容性”为名进行编辑 - 参见此Meta帖子了解详情。 - Krease
8个回答

73

copymutableCopy 定义在不同的协议中(分别是 NSCopyingNSMutableCopying),而且 NSArray 同时遵循这两个协议。 mutableCopy 被定义在 NSArray 中(不只是 NSMutableArray),它可以让你复制一个本来是不可变的数组并使其变成一个可变的副本:

// create an immutable array
NSArray *arr = [NSArray arrayWithObjects: @"one", @"two", @"three", nil ];

// create a mutable copy, and mutate it
NSMutableArray *mut = [arr mutableCopy];
[mut removeObject: @"one"];

总结:

  • 无论原始类型如何,您可以依赖于mutableCopy的结果是可变的。对于数组,结果应为NSMutableArray
  • 不能依赖copy的结果是可变的!复制NSMutableArray 可能会返回一个NSMutableArray,因为那是原始类,但是复制任意NSArray实例不会。

编辑:在Mark Bessey的答案中重新阅读您的原始代码后。当您创建数组的副本时,无论您如何处理副本,都可以仍然修改原始内容。 copymutableCopy会影响新数组是否为可变性。

编辑2:修正了我的(错误)假设,即NSMutableArray - copy将返回一个NSMutableArray


4
将-copy方法应用于NSMutableArray通常不会返回另一个可变数组。虽然它确实“可以这样做”,但通常情况下,它会返回一个不可变的、具有恒定时间访问的数组。 - Mark Bessey
哎呀,你说得对。我误读了 OP 的代码,认为他正在修改 -copy 的结果。NSMutableArray 的 -copy 返回另一个可变数组也是有道理的,所以我(错误地)假设那就是情况。 - Asher Dunn
我是否遗漏了什么,因为我找不到任何关于 [mut remove: @"one"]; 的参考?这不应该是 [mut removeObjectIdenticalTo: @"one"]; 吗?我只是想澄清一下给那些正在阅读并尝试学习的人。 - fuzzygoat
糟糕,应该是[mut removeObject:@"one"];。removeObject使用-isEqual来确定匹配项,如果两个字符串具有相同的内容,则被视为相等。removeObjectIdenticalTo仅删除传递的确切对象(它比较对象地址而不是内容)。[mut removeObjectIdenticalTo:@"one"]创建一个新的临时NSString对象,该对象不在数组中,因此无法从数组中删除。 - Asher Dunn
6
“复制一个NSMutableArray可能会返回一个NSMutableArray”是不正确的。请参见http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmImplementCopy.html#//apple_ref/doc/uid/20000049-999791,无论原始对象是否可变,在涉及到“不可变 vs. 可变”概念的对象上使用NSCopying都会生成不可变的拷贝。” - user102008
讲解得很清楚,回答非常出色。为你点赞,干得好! - Abdul Yasin

9
我认为你可能误解了copy和mutableCopy的工作原理。在你的第一个例子中,myArray_COPY是myArray的不可变拷贝。在制作副本后,您可以操纵原始的myArray的内容,而不会影响myArray_COPY的内容。
在第二个例子中,您创建了myArray的可变副本,这意味着您可以修改数组的任一副本,而不影响另一个副本。
如果我尝试从myArray_COPY中插入/删除对象,则第一个示例将失败,就像您预期的那样。
也许思考一个典型的用例会有所帮助。通常情况下,您可能会编写一个方法来接受一个NSArray *参数,并基本上将其存储以供以后使用。您可以这样做:
- (void) doStuffLaterWith: (NSArray *) objects {
  myObjects=[objects retain];
}

...但是你会面临一个问题,那就是该方法可以使用NSMutableArray作为参数。创建数组的代码可能会在调用doStuffLaterWith:方法之间对其进行操作,而在稍后需要使用该值时。在多线程应用程序中,甚至可以在迭代期间更改数组的内容,这可能会导致一些有趣的错误。

如果你改为这样做:

- (void) doStuffLaterWith: (NSArray *) objects {
  myObjects=[objects copy];
}

然后该副本会在调用该方法时创建一个数组内容的快照。


5
NSMutableArray* anotherArray = [[NSMutableArray alloc] initWithArray:oldArray
                                                           copyItems:YES];

会创建一个名为anotherArray的数组,它是oldArray的深度复制到第二层。如果oldArray中的对象是数组。在大多数应用程序中通常是这种情况。

如果我们需要一个真正的深拷贝,我们可以使用以下方法:

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
    [NSKeyedArchiver archivedDataWithRootObject: oldArray]];

这将确保所有级别都被复制,并保留每个级别原始对象的可变性。
Robert Clarence D'Almeida, 印度班加罗尔。

你对“真正的深度复制”概念的理解非常好,正是我想要的。非常感谢你,给你点赞加一。 - Nirav Gadhiya

5

"copy"方法返回通过实现NSCopying协议中的copyWithZone:方法创建的对象。

如果您向NSString发送一个复制消息:

NSString* myString;

NSString* newString = [myString copy];

返回值将是一个NSString(不可变)


mutableCopy方法返回通过实现NSMutableCopying协议的mutableCopyWithZone:创建的对象:

通过发送以下内容:

NSString* myString;

NSMutableString* newString = [myString mutableCopy];

返回值将会是可变的。


在所有情况下,对象必须实现协议,表示它将创建新的副本对象并将其返回给您。


对于NSArray,关于浅复制和深复制还存在额外的复杂性。

NSArray的浅复制只会复制原始数组中对象的引用,并将它们放入新数组中。

结果是:

NSArray* myArray;

NSMutableArray* anotherArray = [myArray mutableCopy];

[[anotherArray objectAtIndex:0] doSomething];

对原始数组中索引为0的对象也会产生影响。


深拷贝实际上会复制数组中包含的每个单独对象。这是通过向每个单独对象发送“copyWithZone:”消息来完成的。

NSArray* myArray;

NSMutableArray* anotherArray = [[NSMutableArray alloc] initWithArray:myArray
                                                       copyItems:YES];

编辑后删除了我对可变对象复制的错误假设


3
您正在对原始数组调用addObject和removeObjectAtIndex,而不是您制作的新副本。调用copy与mutableCopy仅影响对象的新副本的可变性,而不影响原始对象。

2
简单来说,copy方法返回一个不可变的数组副本,mutableCopy方法返回一个可变的数组副本。
在两种情况下copy方法都表示你得到一个新的数组,“填充”了对原始数组中对象引用(即在副本中引用相同(原始)对象)。如果向mutableCopy添加新对象,则这些对象唯一属于mutableCopy。如果从mutableCopy中删除对象,则它们也从原始数组中删除。
可以将两种情况下的copy方法视为创建副本时原始数组的瞬间快照。

0
-(id)copy always returns a immutable one & -(id)mutableCopy always returns a mutable object,that's it.

你必须知道这些复制操作的返回类型,同时在声明新对象时,被分配的返回值必须是不可变或可变的,否则编译器会显示错误。

被复制的对象不能使用新对象进行修改,它们现在完全是两个不同的对象。


0

假设

NSArray *A = xxx; // A with three NSDictionary objects
NSMutableArray *B = [A mutableCopy]; 

B的内容是NSDictionary对象而不是NSMutableDictionary,对吗?


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