深度复制NSArray


119

是否有任何内置功能可以让我深入复制NSMutableArray

我环顾四周,有人说[aMutableArray copyWithZone:nil]作品很深。但是我尝试过,这似乎是一个浅表。

现在,我正在手动for循环复制:

//deep copy a 9*9 mutable array to a passed-in reference array

-deepMuCopy : (NSMutableArray*) array 
    toNewArray : (NSMutableArray*) arrayNew {

    [arrayNew removeAllObjects];//ensure it's clean

    for (int y = 0; y<9; y++) {
        [arrayNew addObject:[NSMutableArray new]];
        for (int x = 0; x<9; x++) {
            [[arrayNew objectAtIndex:y] addObject:[NSMutableArray new]];

            NSMutableArray *aDomain = [[array objectAtIndex:y] objectAtIndex:x];
            for (int i = 0; i<[aDomain count]; i++) {

                //copy object by object
                NSNumber* n = [NSNumber numberWithInt:[[aDomain objectAtIndex:i] intValue]];
                [[[arrayNew objectAtIndex:y] objectAtIndex:x] addObject:n];
            }
        }
    }
}

但我想要一个更清洁,更简洁的解决方案。


44
@Genericrich的深层副本和浅层副本是软件开发中定义明确的术语。Google.com可能会有所帮助
Andrew Grant

1
可能有些混乱是因为-copy在Mac OS X 10.4和10.5之间,不可变集合上的行为发生了变化:developer.apple.com/library/mac/releasenotes/Cocoa/…(向下滚动到“不可变集合和复制行为”)
用户102008

1
@AndrewGrant关于进一步的思考,并且尊重我,我不同意深拷贝是一个定义明确的术语。根据您阅读的来源,目前尚不清楚是否需要对“深层复制”操作进行无限递归到嵌套数据结构中。换句话说,对于创建成员是原始对象成员的浅表副本的新对象的复制操作是否是“深层复制”操作,您将得到矛盾的答案。有关此内容的一些讨论,请参见stackoverflow.com/a/6183597/1709587(在Java上下文中,但两者都是相同的)。
Mark Amery 2013年

@AndrewGrant我必须备份@MarkAmery和@Genericrich。如果集合中使用的根类及其所有元素都是可复制的,则深层副本定义良好。NSArray(和其他objc集合)不是这种情况。如果某个元素未实现copy,则应将哪些内容放入“深拷贝”中?如果元素是另一个集合,copy则实际上不产生(同一类的)副本。因此,我认为争论特定情况下所需的副本类型是完全正确的。
Nikolai Ruhe 2015年

@NikolaiRuhe如果一个元素没有实现NSCopying/ -copy,则它是不可复制的-因此您永远不要尝试对其进行复制,因为这不是它设计的功能。就Cocoa的实现而言,不可复制对象通常具有绑定到的某些C后端状态,因此入侵对象的直接副本可能会导致竞争状况或更糟。因此,回答“应将哪些内容放入“深拷贝”中” —保留的参考文献。当您有非NSCopying对象时,您唯一可以放在任何地方的东西。
斯利普·汤普森

Answers:


210

正如有关深拷贝Apple文档明确指出:

如果您只需要一级深度副本:

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

上面的代码创建一个新数组,其成员是旧数组的成员的浅表副本。

请注意,如果您需要深度复制整个嵌套数据结构(链接的Apple文档称为真正的深度复制),则此方法将无法满足要求。请在此处查看其他答案。


似乎是正确的答案。API指出每个元素都会收到[element copyWithZone:]消息,这可能是您所看到的。如果实际上您发现发送[NSMutableArray copyWithZone:nil]不会进行深度复制,则使用此方法可能无法正确复制数组数组。
Ed Marty

7
我认为这不会按预期进行。从苹果公司的文档中:“ copyWithZone:方法执行浅表复制。如果您具有任意深度的集合,则为flag参数传递YES将对表面以下的第一级执行不可变的复制。如果您传递NO,则将进行以下操作:第一个级别不受影响。在任何一种情况下,所有更深级别的可变性都不会受到影响
Joe D'Andrea

7
这不是完整的副本
Daij-Djan

9
这是一个不完整的答案。这将产生一个一级的深层副本。如果数组中有更复杂的类型,则不会提供深层副本。
卡梅隆·洛厄尔·帕尔默

7
可以是一个深层副本,具体取决于在copyWithZone:接收类上如何实现。
devios1 2014年

62

我知道轻松做到这一点的唯一方法是存档,然后立即取消存档数组。感觉有点像骇客,但实际上在Apple文档中关于复制collection的建议中明确建议:

如果需要真正的深层副本(例如,当您拥有数组数组时),只要内容全部符合NSCoding协议,则可以存档然后取消存档该集合。清单3显示了此技术的一个示例。

清单3真实的深层副本

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

问题是您的对象必须支持NSCoding接口,因为它将用于存储/加载数据。

Swift 2版本:

let trueDeepCopyArray = NSKeyedUnarchiver.unarchiveObjectWithData(
    NSKeyedArchiver.archivedDataWithRootObject(oldArray))

12
如果阵列很大,则在性能方面,使用NSArchiver和NSUnarchiver是非常繁重的解决方案。编写使用NSCopying协议的通用NSArray类别方法将达到目的,从而导致简单的“保留”不可变对象和真实的“副本”可变对象。
Nikita Zhuk,2009年

谨慎对待费用是件好事,但是NSCoding真的比该方法中使用的NSCopying贵initWithArray:copyItems:吗?考虑到有多少控件类符合NSCoding但不符合NSCopying,这种归档/取消归档的解决方法似乎非常有用。
维也纳,2012年

我强烈建议您不要使用这种方法。序列化永远比仅复制内存快。
布雷特

1
如果您有自定义对象,请确保实现encodeWithCoder和initWithCoder以符合NSCoding协议。
user523234

显然,在使用序列化时,强烈建议程序员自行决定。
VH-NZZ

34

默认情况下,复制会产生浅表复制

这是因为调用与使用默认区域进行复制copy相同copyWithZone:NULL。该copy调用不会产生深拷贝。在大多数情况下,它会为您提供浅表副本,但无论如何都取决于类。要进行全面讨论,我建议在Apple Developer网站上使用Collections编程主题

initWithArray:CopyItems:提供一层深层复制

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:someArray copyItems:YES];

NSCoding 是苹果推荐的提供深层复制的方法

对于真正的深层副本(数组数组),您将需要NSCoding并存档/取消存档对象:

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

1
这是对的。无论是关于内存,性能的流行还是过早优化的极端情况。顺便说一句,这种用于深层复制的ser-derer hack在许多其他语言环境中使用。除非存在obj dedupe,否则这将保证有一个很好的深层副本,该副本与原始副本完全分开。

1
这里有一个不太rottable苹果文档的网址developer.apple.com/library/mac/#documentation/cocoa/conceptual/...

7

对于字典

NSMutableDictionary *newCopyDict = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)objDict, kCFPropertyListMutableContainers);

对于阵列

NSMutableArray *myMutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(NULL, arrData, kCFPropertyListMutableContainersAndLeaves);


4

不,没有任何内置于此的框架。可可集合支持浅拷贝(使用copyarrayWithArray:方法),但甚至不谈论深拷贝概念。

这是因为随着您的集合的内容开始包括您自己的自定义对象,“深层复制”开始变得难以定义。难道“深拷贝”的意思是每一个对象图中的对象是一个独特的相对于基准的每个对象在原来的对象图?

如果有某种假设性的NSDeepCopying协议,您可以进行设置并在所有对象中做出决定,但是不幸的是没有。如果控制了图形中的大多数对象,则可以自己创建并实现此协议,但是需要根据需要向Foundation类添加类别。

@AndrewGrant的答案表明,使用键控存档/取消存档是一种无效的方法,但是对于任意对象而言,这样做都是正确而干净的方法。本书甚至走得那么远,因此建议向所有对象添加一个类别,该类别恰好支持深度复制。


2

如果尝试为JSON兼容数据实现深度复制,我有一个解决方法。

简单地采取NSDataNSArray使用NSJSONSerialization,然后重新创建JSON对象,这将创建一个完整的新的和新的副本NSArray/NSDictionary与他们的新内存的引用。

但请确保NSArray / NSDictionary的对象及其子对象必须是JSON可序列化的。

NSData *aDataOfSource = [NSJSONSerialization dataWithJSONObject:oldCopy options:NSJSONWritingPrettyPrinted error:nil];
NSDictionary *aDictNewCopy = [NSJSONSerialization JSONObjectWithData:aDataOfSource options:NSJSONReadingMutableLeaves error:nil];

1
您必须NSJSONReadingMutableContainers在此问题中指定用例。
Nikolai Ruhe 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.