【问题标题】:Deep copying an NSArray深拷贝一个 NSArray
【发布时间】:2010-10-13 10:23:54
【问题描述】:

是否有任何内置函数可以让我深度复制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];
            }
        }
    }
}

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

【问题讨论】:

  • @Genericrich 深拷贝和浅拷贝是软件开发中定义明确的术语。 Google.com 可能会有所帮助
  • 可能有些混乱是因为-copy 在不可变集合上的行为在 Mac OS X 10.4 和 10.5 之间发生了变化:developer.apple.com/library/mac/releasenotes/Cocoa/…(向下滚动到“不可变集合和复制行为”)跨度>
  • @AndrewGrant 经过进一步思考,恕我直言,我不同意 deep copy 是一个定义明确的术语。根据您阅读的来源,不清楚是否无限递归到嵌套数据结构是“深度复制”操作的要求。换句话说,对于创建其成员是原始对象成员的浅拷贝的新对象的复制操作是否是“深拷贝”操作,您将得到相互矛盾的答案。请参阅stackoverflow.com/a/6183597/1709587 以了解对此的一些讨论(在 Java 上下文中,但它仍然是相关的)。
  • @AndrewGrant 我必须支持@MarkAmery 和@Genericrich。如果集合中使用的根类及其所有元素都是可复制的,则深度复制是明确定义的。 NSArray(和其他 objc 集合)不是这种情况。如果一个元素没有实现copy,应该把什么放入“深拷贝”?如果元素是另一个集合,copy 实际上不会产生(同一类的)副本。所以我认为在特定情况下争论想要的副本类型是完全正确的。
  • @NikolaiRuhe 如果一个元素没有实现NSCopying/-copy,那么它是不可复制的——所以你永远不应该尝试复制它,因为这不是它设计的能力拥有。就 Cocoa 的实现而言,不可复制的对象通常具有一些与之相关的 C 后端状态,因此破解对象的直接副本可能会导致竞争条件或更糟。所以要回答“应该在‘深拷贝’中放入什么”——保留的参考。当你有一个非NSCopying 对象时,你唯一可以放在任何地方的东西。

标签: objective-c cocoa-touch cocoa nsarray deep-copy


【解决方案1】:

正如Apple documentation about deep copies 明确指出的那样:

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

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

上面的代码创建了一个新数组,其成员是旧数组成员的浅拷贝。

请注意,如果您需要深度复制整个嵌套数据结构(链接的 Apple 文档称为 真正的深度复制),那么这种方法将不够用。请在此处查看其他答案。

【讨论】:

  • 似乎是正确的答案。 API 声明每个元素都会收到一条 [element copyWithZone:] 消息,这可能就是您所看到的。如果您实际上看到发送 [NSMutableArray copyWithZone:nil] 不会进行深度复制,那么使用此方法可能无法正确复制数组数组。
  • 我认为这不会按预期工作。来自 Apple 文档:“copyWithZone: 方法执行 浅复制。如果您有一个任意深度的集合,则为 flag 参数传递 YES 将执行表面以下第一层的不可变副本。如果您通过 NO,则第一级的可变性不受影响。在任何一种情况下,所有更深层次的可变性都不受影响。” SO 问题涉及深可变副本。
  • 这是一个不完整的答案。这会产生一层深拷贝。如果 Array 中有更复杂的类型,则不会提供深拷贝。
  • @resting 因为它是NSArray 方法,不是NSMutableArray 独有的。
  • 这个可以是深拷贝,取决于copyWithZone:在接收类上的实现方式。
【解决方案2】:

我知道轻松做到这一点的唯一方法是存档,然后立即取消存档您的阵列。感觉有点像 hack,但实际上在 Apple Documentation on copying collections 中明确建议,其中指出:

如果您需要真正的深拷贝,例如当您有一个数组数组时,您可以先归档然后再取消归档该集合,前提是所有内容都符合 NSCoding 协议。清单 3 显示了这种技术的一个示例。

清单 3 一个真正的深拷贝

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

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

Swift 2 版本:

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

【讨论】:

  • 如果您的阵列很大,则使用 NSArchiver 和 NSArchiver 在性能方面是一个非常繁重的解决方案。编写一个使用 NSCopying 协议的通用 NSArray 类别方法可以解决问题,导致不可变对象的简单“保留”和可变对象的真正“副本”。
  • 对费用要谨慎是好事,但NSCoding真的会比initWithArray:copyItems:方法中使用的NSCopying更贵吗?考虑到有多少控件类符合 NSCoding 但不符合 NSCopying,这种归档/取消归档解决方法似乎非常有用。
  • 我强烈建议您不要使用这种方法。序列化永远不会比复制内存更快。
  • 如果您有自定义对象,请确保实现 encodeWithCoder 和 initWithCoder 以符合 NSCoding 协议。
  • 显然,强烈建议程序员在使用序列化时自行决定。
【解决方案3】:

Copy 默认提供浅拷贝

这是因为调用copycopyWithZone:NULL 相同,也称为使用默认区域进行复制。 copy 调用不会产生深拷贝。在大多数情况下,它会给你一个浅拷贝,但无论如何它取决于类。对于全面的讨论,我推荐 Apple Developer 网站上的 Collections Programming Topics

initWithArray:CopyItems: 提供一级深拷贝

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

NSCoding 是 Apple 推荐的提供深拷贝的方式

对于真正的深拷贝(数组数组),您需要NSCoding 并归档/取消归档对象:

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

【讨论】:

  • 这是正确的。无论关于内存、性能的流行或过早优化的边缘情况如何。顺便说一句,这种用于深度复制的 ser-derser hack 用于许多其他语言环境。除非有 obj 重复数据删除,否则这可以保证良好的深层副本与原始副本完全分离。
  • 这是一个不易腐烂的 Apple 文档 url developer.apple.com/library/mac/#documentation/cocoa/conceptual/…
【解决方案4】:

对于字典

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

对于数组

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

【讨论】:

    【解决方案5】:

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

    这是因为“深拷贝”开始变得难以定义,因为您的集合的内容开始包含您自己的自定义对象。 “深拷贝”是否意味着对象图中的每个对象都是相对于原始对象图中的每个对象的唯一引用?

    如果有一些假设的NSDeepCopying 协议,您可以设置它并在所有对象中做出决定,但不幸的是没有。如果您控制了图中的大部分对象,您可以自己创建此协议并实现它,但您需要根据需要向 Foundation 类添加一个类别。

    @AndrewGrant 的回答建议使用键控归档/取消归档是一种性能不佳但正确且干净的方式,可以为任意对象实现此目的。 This book even goes so far so suggest 为所有支持深度复制的对象添加一个类别。

    【讨论】:

      【解决方案6】:

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

      只需使用NSJSONSerialization 获取NSArray 中的NSData,然后重新创建JSON 对象,这将创建一个全新的NSArray/NSDictionary 的全新副本以及它们的新内存引用。

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

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

      【讨论】:

      • 你必须为这个问题的用例指定NSJSONReadingMutableContainers
      猜你喜欢
      • 2013-09-07
      • 1970-01-01
      • 2012-04-12
      • 2015-01-13
      • 2011-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-28
      相关资源
      最近更新 更多