Как сделать точную копию массива?

101

Как мне сделать точную копию массива?

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

Я пробовал использовать .copy()

var originalArray = [1, 2, 3, 4]
var duplicateArray = originalArray.copy()
Патрик
источник
5
почему бы вам не присвоить значение прямо вот так:var duplicateArray = originalArray
Дхармеш Хени
1
В моем случае это не работает. Это создает другой объект, который является просто ссылкой на тот же массив, и в итоге вы получаете две переменные, ссылающиеся на один и тот же массив.
user1060500 02

Ответы:

177

В 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

Обратите внимание, что здесь есть подводные камни, от которых семантика значений 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 не похоже, что он копируется, знаете почему?
Фил Нидертшайдер
@PNGamingPower: x содержит адреса. y содержит копии этих адресов. Если вы измените x [0], y [0] не изменится. (попробуйте x [0] = x [1], y [0] не изменится). Таким образом, y является глубокой копией x, но вы скопировали только указатели, а не то, на что они указывают.
ragnarius 09
@ragnarius, поэтому в основном мы должны определить, что означает «копировать», копирование указателя или значения. Следовательно, это решение для копирования / дублирования массива указателей, но как вы дублируете массив значений? Гол будет, x[0] == x[1]но x[0] === y[0]не удастся
Фил Нидертшайдер
Это должен быть принятый ответ, поскольку семантика значений массива делает ненужной «копию» массива.
Скотт Ахтен
У меня это не работает. Если я буду следовать этому методу, я получу две ссылки, которые в конечном итоге указывают на один и тот же массив объектов. Если я удаляю элемент из списка, он отражается в обеих ссылках на объекты, поскольку список не был скопирован, а был просто указан объект.
user1060500 02
29

Нейт прав. Если вы работаете с примитивными массивами, все, что вам нужно сделать, это назначить duplicateArray исходному массиву.

Для полноты картины, если вы работаете с объектом NSArray, вы должны сделать следующее, чтобы сделать полную копию NSArray:

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

var duplicateArray = NSArray(array:originalArray, copyItems: true)
applejack42
источник
Это круто! Спасибо!
Патрик
25

Есть третий вариант ответа Нейта:

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

* ИЗМЕНИТЬ * редактирование начинается здесь

Выше, по сути, то же самое, что и ниже, и на самом деле использование оператора равенства ниже будет работать лучше, поскольку массив не будет скопирован, если он не будет изменен (это сделано намеренно).

let z = x

Подробнее см. Здесь: https://developer.apple.com/swift/blog/?id=10.

* ИЗМЕНИТЬ * редактирование заканчивается здесь

добавление или удаление этого массива не повлияет на исходный массив. Однако изменение любых свойств объектов, содержащихся в массиве, будет видно в исходном массиве. Поскольку объекты в массиве не являются копиями (при условии, что массив содержит объекты, а не примитивные числа).

oyalhi
источник
это действительно эффект, я это тестировал. есть два массива, если u изменится на 1, будет
Filthy Knight
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 }
Джон Панг
Если вы используете Swift BufferPointers, эту версию следует использовать в качестве прямой копии. Перед изменением значения в исходном или скопированном массиве 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
    }
}

и это почти все, чтобы просмотреть код и образец, проверьте эту суть

Сохайб Хассун
источник
Это сэкономило много времени, спасибо.
Abhijit
Для подклассов протокол не может гарантировать, что требование init реализовано с типом подкласса. Вы объявляете протокол копирования, который реализует для вас копирование, но вы все еще реализуете clone (), что не имеет смысла.
Binarian
1
@iGodric copy предназначен для элементов в коллекции, а clone - для всей коллекции, доступной, когда копия реализована для ее элементов. Есть смысл? Кроме того, компилятор гарантирует, что подклассы действительно следуют протоколу, требуемому их родительским элементом.
johnbakers
@johnbakers О да, теперь я это вижу. Спасибо за объяснение.
Binarian
Очень чистая реализация и избегает ненужной суеты передачи каких-либо параметров в object'sфункцию init
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
Номан Харун
источник