复制一个NSArray,其中包含原始元素的可变副本

12
我正在一个类中创建一个字典数组。我想将该数组的副本返回给任何请求它的其他对象。这个传递给其他对象的副本需要在不修改原始副本的情况下进行修改。
因此,我在我的“主”数组持有类的getter方法中使用以下内容:
[[NSMutableArray alloc] initWithArray:masterArray copyItems:YES];

然而,这似乎会使得所有内部的字典都是不可变的。我该如何避免这种情况?

我觉得我在这里漏掉了什么。任何帮助将不胜感激!

4个回答

14

你可以采用另一种方法,使用CFPropertyListCreateDeepCopy()函数(在CoreFoundation框架中),将mutabilityOption参数设置为kCFPropertyListMutableContainers。代码如下:

NSMutableArray* originalArray;
NSMutableArray* newArray;

newArray = (NSMutableArray*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFPropertyListRef)originalArray, kCFPropertyListMutableContainers);

这将不仅创建字典的可变副本,还将递归地创建其中包含的任何东西的可变副本。请注意,这只有在您的字典数组中只包含有效的属性列表对象(数组,数字,日期,数据,字符串和字典)时才会起作用,因此在特定情况下可能或可能不适用。


1
就像任何其他执行“创建”或“复制”的核心基础功能一样,您需要在使用完返回值后负责释放它。 - Brian Webster

6

复制一个数组将创建一个数组对象的副本,其中包含对原始内容对象的引用(适当保留)。

根据文档,-initWithArray:copyItems: 会使用原始数组中的副本来初始化新数组。这个副本是通过向原始内容对象发送 -copyWithZone: 来创建的,在可变对象的情况下会创建一个不可变的副本。

如果您需要不同的行为(例如内容对象的可变副本或深层副本),则必须编写自己的便捷函数/方法来实现。


4
那真是一个非常愚蠢的修改,Quinn。像原文中一样用逗号来偏移那个短语是完全可以接受的。如果你要当语法纳粹分子,甚至在括号里面“i.e.”后面仍然需要加一个逗号。 - Jason Coco
7
更不用说括号后面的内容了。但是,每次编辑都是向社区维基化某个人的帖子迈出的一步(http://stackoverflow.com/questions/128434/what-are-community-wiki-posts-on-stack-overflow/128436#128436),因此让我们不要浪费它们在“修正”他人语法上,唯一的例外是数字恢复一个无法阅读的帖子。 - Peter Hosey

5
一种方法是滥用Key-Value Coding向每个字典发送mutableCopy,并向每个副本发送autorelease。但那是一个肮脏的、不好的技巧,所以不要这样做。事实上,你可能在第一时间就不应该这么做。
通常,当我看到“字典数组”这个词时,我会觉得你正在使用字典来代替模型对象。别这样做。编写自己的模型类;当你有自己的自定义属性和行为方法时,一切都变得更加容易。(某些东西比其他东西更容易:如果没有适当的模型层,实现AppleScript支持几乎是不可能的。)
一旦你拥有真正的模型对象,你可以在其中实现NSCopying,而不需要担心可变性与不可变性,因为你的真正模型类中可能不会有可变性区分。(我不知道其他人怎么想,但在我的模型类中从未做出过这样的区分。)然后你只需使用现有的NSArray initWithArray:copyItems:方法即可。

请不要提及像这样在新手可以看到并被误导的地方使用-valueForKey:这样的东西。他们会被误导的,你知道的:“在互联网上我读到做可变拷贝的方法是…”这就是它将被记住的方式。 - Chris Hanson
我已经修改了那段话,使其不那么具体,并更强烈地建议不要这样做。现在你怎么看? - Peter Hosey

2

关于-initWithArray:copyItems,Jim是正确的,它会向每个元素发送一个-copyWithZone:消息。如果要获得数组元素的可变副本,您需要向每个元素发送-mutableCopyWithZone:(或者为了简洁起见,只需使用-mutableCopy)。这很简单:

NSMutableArray *masterArray = ...
NSMutableArray *clone = [NSMutableArray arrayWithCapacity:[masterArray count]];
for (id anObject in masterArray)
    [clone addObject:[anObject mutableCopy]]; // OR [clone addObject:anObject];

然而,在你解释问题时隐藏了一个更深层次的问题:似乎你希望数组及其元素(字典)都是可变的,但是有一些细节需要澄清,特别是考虑到你的规定:“传递给其他对象的副本需要在不修改原始副本的情况下进行修改。”根据你的意思,这可能非常复杂,需要保证和实现。
例如,假设原始数组包含多个可变字典。创建这两个级别的可变副本意味着获得副本的人可以在不直接更改原始数组或字典的情况下修改自己的数组和数组中的字典。但是,如果字典包含可变对象(如NSMutableArray、NSMutableString等),使用“copy”的代码可能会间接修改字典的内容,只使用对可变副本的引用即可。
可变副本是“浅拷贝”,意味着只拷贝第一层,副本具有指向与原始结构相同的元素的指针。因此,保证原始副本和副本之间没有链接(至少手动上)需要遍历整个结构并进行复制。如果某些元素不符合NSCopying或NSMutableCopying,则这可能变得更加复杂。
最简单和最快速的解决方案是使用上面的简单代码或只返回主数组的引用。这要求信任客户端代码不会修改数组,因此在所有情况下都可能无法使用,但如果你也控制调用代码,则可能更可取。另一方面,如果你确实想要完全独立的副本,请考虑利用NSCoding / keyed archiving。
NSMutableArray *masterArray = ...
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:masterArray];
NSMutableArray *clone = [NSKeyedUnarchiver unarchiveObjectWithData:data];

基本上,这将所有内容转换为原始数据字节,然后重新组合成一组新的对象。为了使其正常工作,字典中的所有对象都必须符合NSCoding协议。这可能需要一些操作,但这是一种优雅的通用方法,可确保对象唯一性。当然,这肯定会带来一定的性能成本,但如果您绝对需要保证没有任何副作用,那么它应该可以胜任。


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