如何制作阵列的精确副本?


100

我将如何精确复制数组?

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

我尝试使用 .copy()

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

5
您为什么不这样直接分配值:var duplicateArray = originalArray
Dharmesh Kheni 2015年

1
就我而言,这不起作用。这将创建另一个对象,该对象只是对同一数组的引用,最终您将获得2个引用同一数组的变量。
user1060500'9

Answers:


176

数组在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方法仅返回对其自身的引用,因此上面的测试将产生意外的结果。


我使用简单的代码在操场上尝试了此操作,var x = [UIView(), UIView(), UIView()] var y = x for i in x { NSLog("%p", i) } println("---") for i in y { NSLog("%p", i) }并得到以下输出:0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 ---0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 看起来好像没有被复制,您知道为什么吗?
Phil Niedertscheider 2015年

@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 2015年

这应该是可以接受的答案,因为Array的值语义使得不需要“复制”数组。
Scott Ahten

这对我不起作用。如果遵循该方法,则会得到两个引用,这些引用最终指向同一对象数组。如果我从列表中删除一项,则该项目会反映在两个对象引用中,因为未复制列表,而是仅引用了该对象。
user1060500

28

内特是正确的。如果您正在使用基本数组,则只需将重复数组分配给originalArray。

为了完整起见,如果您使用的是NSArray对象,则可以执行以下操作以完整复制NSArray:

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

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

这很棒!谢谢!
Patrick

23

内特的答案还有第三种选择:

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

*编辑*编辑从这里开始

上面的代码与下面的代码基本相同,实际上,使用下面的相等运算符会更好,因为除非更改数组,否则数组不会被复制(这是设计使然)。

let z = x

在此处阅读更多信息:https : //developer.apple.com/swift/blog/?id=10

*编辑*编辑到此结束

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


它的确有效果,我已经测试过了。有两个数组,如果u更改为1,则第二个数组生效
肮脏的骑士

1
不,不是这样,除非数组保存原始类型而不是对象。然后确实会如答案中所述。一个简单的测试案例:var array1: [String] = ["john", "alan", "kristen"]; print(array1); var array2 = array1.map { $0 }; print(array2); array2[0] = "james"; print(array1); print(array2);
oyalhi

1
请参阅我为使用自定义类创建的更好示例而创建的要点:gist.github.com/oyalhi/3b9a415cf20b5b54bb3833817db059ce
oyalhi

如果您的班级支持NSCopying,则复制一个数组:let z = x.map { $0.copy as! ClassX }
John Pang

如果使用Swift的BufferPointers,则应将此版本用作其直接副本。在更改原始数组或复制的数组中的值之前,Swift会将原始值复制到副本中,然后继续。如果改用Pointers,则Swift不会在发生更改时或发生更改时进行更改,因此您可能最终会更改两个数组。
贾斯汀·甘泽

16

对于普通对象,可以做的是实现一个支持复制的协议,并使对象类像下面这样实现该协议:

protocol Copying {
    init(original: Self)
}

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

然后是用于克隆的Array扩展:

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

仅此而已,查看代码和示例检查了要点


节省了很多时间,谢谢。
阿披吉特(Abhijit)

对于子类,协议不能保证使用子类的类型来实现需求初始化。您正在声明一个复制协议,该协议可以为您实现复制,但是您仍在实现clone(),这没有任何意义。
Binarian

1
@iGodric复制用于集合中的元素,克隆用于整个集合,当对其元素实现复制时可用。合理?而且,编译器确保子类确实遵循其父级所要求的协议。
johnbakers

@johnbakers哦,是的,现在我明白了。感谢您的解释。
Binarian

非常干净的实现,避免了在object'sinit函数中传递任何参数的不必要的麻烦
Sylvan D Ash,

0

如果要复制某个类对象数组的项目。然后,您可以在不使用NSCopying协议的情况下遵循以下代码,但是您需要具有一个初始化方法,该方法应采用对象所需的所有参数。这是在操场上测试的示例代码。

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
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.