如何制作一个精确的数组副本?

125

如何制作一个数组的完全副本?

我很难找到有关在 Swift 中复制数组的信息。

我尝试使用 .copy()

var originalArray = [1, 2, 3, 4]
var duplicateArray = originalArray.copy()

5
为什么不直接像这样赋值:var duplicateArray = originalArray - Dharmesh Kheni
2
这在我的情况下不起作用。这会创建另一个对象,它只是对同一数组的引用,并且最终你会得到两个变量引用同一数组。 - user1060500
5个回答

213

在Swift中,数组具有完整的值语义,因此不需要任何花哨的东西。

var duplicateArray = originalArray就是你所需要的。


如果您的数组内容是引用类型,则是的,这只会复制指向对象的指针。如果要深度复制内容,则可以使用map并对每个实例执行副本操作。对于符合NSCopying协议的Foundation类,可以使用copy()方法:

let x = [NSMutableArray(), NSMutableArray(), NSMutableArray()]
let y = x
let z = x.map { $0.copy() }

x[0] === y[0]   // true
x[0] === z[0]   // false
请注意,这里存在陷阱,Swift的值语义正在努力保护你免受它们的影响。例如,由于NSArray表示一个不可变数组,其copy方法只是返回对自身的引用,因此上面的测试将产生意外的结果。

我在playground中尝试了这个简单的代码 `var x = [UIView(), UIView(), UIView()] var y = xfor i in x { NSLog("%p", i) } println("---") for i in y { NSLog("%p", i) },并得到了以下输出:0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 ---0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770`。看起来好像没有被复制,你知道为什么吗? - Phil Niedertscheider
@PNGamingPower:x包含地址,y包含这些地址的副本。如果您更改x [0],则y [0]不会更改(尝试x [0] = x [1],y [0]不会更改)。因此,y是x的深层副本。但是,您只复制了指针,而不是它们指向的内容。 - ragnarius
@ragnarius 所以基本上我们必须定义“复制”是指复制指针还是值。因此,这是复制/复制指针数组的解决方案,但如何复制值数组?目标是 x[0] == x[1]x[0] === y[0] 应该失败。 - Phil Niedertscheider
这应该是被接受的答案,因为数组的值语义使得不必要地“复制”数组。 - Scott Ahten
这对我不起作用。如果我按照那种方法操作,我会得到两个引用,它们最终指向同一个对象数组。如果我从列表中删除一个项目,由于列表没有被复制,而是只是被引用,所以两个对象引用都会反映出来。 - user1060500

35

对于 Nate 的回答,还有一种第三个选项:

let z = x.map { $0 }  // different array with same objects

*编辑* 编辑从这里开始

上面的代码与下面的代码本质相同,实际上在下面使用等号运算符会更有效率,因为除非数组被更改,否则不会复制该数组(这是设计上的考虑)。

let z = x

阅读更多请点击:https://developer.apple.com/swift/blog/?id=10

* 编辑 * 编辑结束

向此数组中添加或删除内容不会影响原始数组。但是,更改数组所持有的任何对象的任何属性将在原始数组中看到。因为数组中的对象不是副本(假设数组包含的是对象,而不是基本数字)。


1
它确实会产生影响,我已经测试过了。有两个数组,如果你在其中一个进行更改,第二个也会受到影响。 - IDev
1
不会影响,除非数组保存的是原始类型而不是对象。然后它会像答案中所述的一样产生影响。一个简单的测试用例:var array1:[String] = ["john","alan","kristen"]; print(array1); var array2 = array1.map { $0 }; print(array2); array2 [0] =“james”; print(array1); print(array2) - oyalhi
2
请查看我创建的Gist,以使用自定义类为例子:https://gist.github.com/oyalhi/3b9a415cf20b5b54bb3833817db059ce - oyalhi
如果你的类支持 NSCopying,那么可以复制一个数组:let z = x.map { $0.copy as! ClassX } - John Pang
如果使用Swift的BufferPointers,则应使用此版本,因为它是直接副本。 在更改原始数组或复制数组中的值之前,Swift将复制原始值到副本,然后继续执行。如果您使用指针而不是BufferPointers,则Swift将不知道何时发生更改,因此您可能最终会更改两个数组。 - Justin Ganzer

33

Nate是正确的。如果你正在使用原始数组,你只需要将duplicateArray分配给originalArray。

为了完整起见,如果你正在使用NSArray对象,你需要执行以下操作来进行一个完全的NSArray复制:

var originalArray = [1, 2, 3, 4] as NSArray

var duplicateArray = NSArray(array:originalArray, copyItems: true)

太好了!谢谢你! - Patrick

18

对于普通对象,可以实现一个支持复制的协议,并让对象类实现这个协议,例如:

protocol Copying {
    init(original: Self)
}

extension Copying {
    func copy() -> Self {
        return Self.init(original: self)
    }
}

然后是用于克隆的数组扩展:

extension Array where Element: Copying {
    func clone() -> Array {
        var copiedArray = Array<Element>()
        for element in self {
            copiedArray.append(element.copy())
        }
        return copiedArray
    }
}

这就是大概的内容,要查看代码和示例,请查看gist


这节省了很多时间,谢谢。 - Abhijit
对于子类,协议无法保证要求的 init 方法是使用子类类型实现的。您正在声明一个复制协议来为您实现 copy 方法,但您仍然在实现 clone() 方法,这是没有意义的。 - Binarian
1
@iGodric copy 用于集合中的元素,而 clone 用于整个集合,在 copy 实现其元素时可用。有意义吗?此外,编译器确保子类遵循其父类所需的协议。 - johnbakers
@johnbakers 哦,现在我明白了。谢谢你的解释。 - Binarian
非常干净的实现,避免了在“对象”的初始化函数中传递任何参数的不必要麻烦。 - Sylvan D Ash
你会如何修改它以便用于一个包含数组的元素数组? - Recusiwe

0
如果您想要复制某个类对象的数组项。 则可以按照以下代码操作,而无需使用 NSCopying 协议,但需要一个 init 方法,该方法应接受所有所需参数以创建对象。 以下是用于示例测试的代码。
class ABC {
    
    var a = 0
    func myCopy() -> ABC {
        
        return ABC(value: self.a)
    }
    
    init(value: Int) {
        
        self.a = value
    }
}

var arrayA: [ABC] = [ABC(value: 1)]
var arrayB: [ABC] = arrayA.map { $0.myCopy() }

arrayB.first?.a = 2
print(arrayA.first?.a)//Prints 1
print(arrayB.first?.a)//Prints 2

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