Làm cách nào để tạo một bản sao chính xác của một mảng?


100

Làm cách nào để tạo một bản sao chính xác của một mảng?

Tôi đang gặp khó khăn khi tìm thông tin về việc sao chép một mảng trong Swift.

Tôi đã thử sử dụng .copy()

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

5
tại sao không bạn giá trị assign trực tiếp như thế này:var duplicateArray = originalArray
Dharmesh Kheni

1
Điều đó không hiệu quả trong trường hợp của tôi. Điều đó tạo ra một đối tượng khác chỉ là một tham chiếu đến cùng một mảng và bạn kết thúc với 2 biến tham chiếu đến cùng một mảng.
user1060500

Câu trả lời:


176

Mảng có đầy đủ ngữ nghĩa giá trị trong Swift, vì vậy không cần bất cứ thứ gì cầu kỳ.

var duplicateArray = originalArray là tất cả những gì bạn cần.


Nếu nội dung của mảng của bạn là một kiểu tham chiếu thì có, điều này sẽ chỉ sao chép các con trỏ đến các đối tượng của bạn. Để thực hiện sao chép sâu nội dung, thay vào đó, bạn sẽ sử dụng mapvà thực hiện sao chép từng phiên bản. Đối với các lớp Foundation tuân theo NSCopyinggiao thức, bạn có thể sử dụng copy()phương pháp:

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

x[0] === y[0]   // true
x[0] === z[0]   // false

Lưu ý rằng có những cạm bẫy ở đây mà ngữ nghĩa giá trị của Swift đang hoạt động để bảo vệ bạn — ví dụ: vì NSArrayđại diện cho một mảng bất biến, copyphương thức của nó chỉ trả về một tham chiếu cho chính nó, vì vậy thử nghiệm ở trên sẽ mang lại kết quả không mong muốn.


Tôi đã thử điều này trong sân chơi với mã đơn giản này var x = [UIView(), UIView(), UIView()] var y = x for i in x { NSLog("%p", i) } println("---") for i in y { NSLog("%p", i) }và tôi nhận được kết quả này: 0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 ---0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 Có vẻ như nó đang được sao chép, bạn có biết tại sao không?
Phil Niedertscheider

@PNGamingPower: x chứa địa chỉ. y chứa các bản sao của các địa chỉ đó. Nếu bạn thay đổi x [0], y [0] sẽ không thay đổi. (thử x [0] = x [1], y [0] sẽ không thay đổi). Vì vậy, y là một bản sao sâu của x, nhưng bạn chỉ sao chép các con trỏ chứ không phải những gì chúng đang trỏ tới.
ragnarius

@ragnarius vì vậy về cơ bản chúng ta phải xác định "copy" nghĩa là gì, sao chép con trỏ hoặc giá trị. Do đó, đây là giải pháp để sao chép / nhân bản mảng con trỏ, nhưng làm thế nào để bạn nhân bản mảng giá trị? Mục tiêu sẽ là x[0] == x[1]nhưng x[0] === y[0]sẽ thất bại
Phil Niedertscheider

Đây phải là câu trả lời được chấp nhận, vì ngữ nghĩa giá trị của Mảng làm cho "bản sao" của mảng không cần thiết.
Scott Ahten

Điều này không hiệu quả với tôi. Nếu tôi làm theo phương pháp đó, tôi nhận được hai tham chiếu cuối cùng trỏ đến cùng một mảng đối tượng. Nếu tôi xóa một mục khỏi danh sách, nó được phản ánh trong cả hai tham chiếu đối tượng vì danh sách không được sao chép, mà đối tượng chỉ được tham chiếu.
user1060500

28

Nate là chính xác. Nếu bạn đang làm việc với các mảng nguyên thủy, tất cả những gì bạn cần làm là gán DupateArray cho originalArray.

Để hoàn thiện, nếu bạn đang làm việc với một đối tượng NSArray, bạn sẽ làm như sau để tạo bản sao đầy đủ của một NSArray:

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

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

Điều đó thật tuyệt! Cảm ơn bạn!
Patrick

23

Có một lựa chọn thứ ba cho câu trả lời của Nate:

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

* ĐÃ CHỈNH SỬA * chỉnh sửa bắt đầu ở đây

Bên trên về cơ bản giống như bên dưới và thực sự sử dụng toán tử bình đẳng bên dưới sẽ hoạt động tốt hơn vì mảng sẽ không được sao chép trừ khi nó được thay đổi (đây là do thiết kế).

let z = x

Đọc thêm tại đây: https://developer.apple.com/swift/blog/?id=10

* ĐÃ CHỈNH SỬA * chỉnh sửa kết thúc tại đây

thêm hoặc bớt vào mảng này sẽ không ảnh hưởng đến mảng ban đầu. Tuy nhiên, việc thay đổi bất kỳ thuộc tính nào của đối tượng mà mảng đó nắm giữ sẽ được nhìn thấy trong mảng ban đầu. Vì các đối tượng trong mảng không phải là bản sao (giả sử mảng chứa các đối tượng chứ không phải số nguyên thủy).


nó có hiệu lực, tôi đã thử nghiệm nó. có hai mảng, nếu u thay đổi trong 1, thứ hai được thực hiện
Filthy Hiệp sĩ

1
Không, trừ khi mảng chứa các kiểu nguyên thủy thay vì các đối tượng. Sau đó, nó ảnh hưởng như đã nêu trong câu trả lời. Một trường hợp thử nghiệm đơn giản:var array1: [String] = ["john", "alan", "kristen"]; print(array1); var array2 = array1.map { $0 }; print(array2); array2[0] = "james"; print(array1); print(array2);
oyalhi

1
Xin vui lòng xem ý chính này tôi đã tạo cho ví dụ tốt hơn sử dụng một lớp tùy chỉnh: gist.github.com/oyalhi/3b9a415cf20b5b54bb3833817db059ce
oyalhi

Nếu lớp học của bạn hỗ trợ NSCopying, thì hãy sao chép một mảng:let z = x.map { $0.copy as! ClassX }
John Pang

Nếu sử dụng BufferPointers của Swift, đây là phiên bản bạn nên sử dụng làm bản sao trực tiếp của nó. Trước khi thay đổi một giá trị trong mảng gốc hoặc mảng đã sao chép, Swift sẽ sao chép các giá trị của bản gốc vào bản sao rồi tiếp tục. Nếu bạn sử dụng Con trỏ thay thế, Swift sẽ không có ngay bây giờ nếu hoặc khi các thay đổi diễn ra, do đó, bạn có thể sẽ thay đổi cả hai mảng.
Justin Ganzer

16

Đối với các đối tượng bình thường, điều có thể làm là thực hiện một giao thức hỗ trợ sao chép và làm cho lớp đối tượng thực hiện giao thức này như sau:

protocol Copying {
    init(original: Self)
}

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

Và sau đó là phần mở rộng Mảng để sao chép:

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

và đó là khá nhiều, để xem mã và mẫu hãy kiểm tra ý chính này


Điều này đã tiết kiệm rất nhiều thời gian, Cảm ơn bạn.
Abhijit

Đối với các lớp con, giao thức không thể đảm bảo rằng init yêu cầu được thực hiện với kiểu của lớp con. Bạn đang khai báo một giao thức sao chép thực hiện bản sao cho bạn, nhưng bạn vẫn đang triển khai clone (), điều đó không có ý nghĩa.
Binarian

1
Bản sao @iGodric dành cho các phần tử trong bộ sưu tập và bản sao dành cho toàn bộ bộ sưu tập, khả dụng khi bản sao được triển khai cho các phần tử của nó. Có lý? Ngoài ra, trình biên dịch đảm bảo rằng các lớp con tuân theo giao thức cha của chúng yêu cầu.
johnbakers

@johnbakers Ồ vâng, giờ thì tôi thấy rồi. Cám ơn vì sự giải thích.
Binarian

Thực hiện rất sạch sẽ và tránh được sự hối hả không cần thiết của đi qua trong bất kỳ tham số trong object'shàm init
Sylvan D Ash

0

Nếu bạn muốn sao chép các mục của một mảng của một số đối tượng lớp. Sau đó, bạn có thể làm theo mã bên dưới mà không sử dụng giao thức NSCopying nhưng bạn cần có một phương thức init sẽ nhận tất cả các tham số cần thiết cho đối tượng của bạn. Đây là mã cho một ví dụ để kiểm tra trên sân chơi.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.