Як зробити точну копію масиву?


100

Як я можу зробити точний дублікат масиву?

Мені важко знайти інформацію про дублювання масиву в Swift.

Я спробував використовувати .copy()

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

5
чому ти не присвоюєш значення прямо так:var duplicateArray = originalArray
Dharmesh Kheni

1
У моєму випадку це не працює. Це створює ще один об'єкт, який є лише посиланням на той самий масив, і ви отримуєте дві змінні, що посилаються на той же масив.
користувач1060500

Відповіді:


176

Масиви мають повноцінну семантику в Swift, тому не потрібно нічого фантазії.

var duplicateArray = originalArray це все, що вам потрібно.


Якщо вміст масиву є еталонним типом, то так, це скопіює покажчики лише на ваші об’єкти. Щоб виконати глибоку копію вмісту, замість цього слід використовувати mapта виконувати копію кожного примірника. Для класів Foundation, які відповідають NSCopyingпротоколу, ви можете використовувати 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

Зауважте, що тут є підводні камені, над якими семантика значень Свіфта працює над тим, щоб захистити вас, наприклад, оскільки він 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

@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]повинна бути невдалою
Філ Нідерцшайдер

Це має бути прийнятою відповіддю, оскільки семантика значення масиву робить «копію» масиву непотрібною.
Скотт Ахтен

Це не працює для мене. Якщо я дотримуюся цього методу, я отримую два посилання, які в кінцевому підсумку вказують на один і той же масив об'єктів. Якщо я видаляю елемент зі списку, він відображається в обох посиланнях на об'єкти, оскільки список не був скопійований, а навпаки, на об’єкт було лише посилання.
користувач1060500

28

Нейт правильно. Якщо ви працюєте з примітивними масивами, все, що вам потрібно зробити, це призначити duplicateArray для originalArray.

Для повноти, якби ви працювали об'єктом NSArray, ви зробите наступне, щоб зробити повну копію NSArray:

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

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

Це чудово! Дякую!
Патрік

23

Існує третій варіант відповіді Нейт:

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

* РЕДАКТИВАНО * редагування починається тут

Вище по суті те саме, що нижче, і фактично використання оператора рівності нижче буде краще, оскільки масив не буде скопійовано, якщо він не буде змінено (це за задумом).

let z = x

Детальніше читайте тут: https://developer.apple.com/swift/blog/?id=10

* Редаговано * редагування закінчується тут

додавання або видалення до цього масиву не вплине на початковий масив. Однак зміна будь-яких властивостей будь-якого з об'єктів, якими володіє масив, буде видно у вихідному масиві. Тому що об'єкти в масиві не є копіями (якщо припустити, що масив містить об'єкти, а не примітивні числа).


це робить ефект, я його протестував. є два масиви, якщо ви змінюєте 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 }
Джон Панг,

Якщо ви використовуєте BufferPointers Swift, це версія, яку ви повинні використовувати як пряму копію. Перш ніж змінити значення або в оригіналі, або в скопійованому масиві, Swift скопіює значення оригіналу в копію, а потім продовжить. Якщо ви замість цього використовуєте Покажчики, 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
    }
}

і це майже все, щоб переглянути код і зразок перевірити цю суть


Це зекономило багато часу, дякую.
Абхіджіт

Для підкласів протокол не може гарантувати, що вимога init реалізована з типом підкласу. Ви декларуєте протокол копіювання, який реалізує копію для вас, але ви все ще реалізуєте clone (), що не має сенсу.
Бінаріан

1
@iGodric копія призначена для елементів колекції, а клон - для всієї колекції, доступна, коли копія реалізована для її елементів. Мати сенс? Крім того, компілятор забезпечує, щоб підкласи виконували протокол, який вимагає їхній батько.
johnbakers

@johnbakers О так, зараз я це бачу. Дякую за пояснення.
Бінаріан

Дуже чиста реалізація та дозволяє уникнути зайвої суєти передачі будь-яких параметрів у функції object'sinit
Sylvan D Ash

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.