1. Почему значения меняются в обоих массивах? Array это value type, но после присваивания меняются элементы и в копии, и в оригинале.
2. Как изменить код так, чтобы изменение в одном массиве не затрагивало другой? Один способ: сделать Entity структурой, чтобы у элементов была семантика value type. Есть ли другие способы?
Темы
Компании
Ответ
Сразу обращаем внимание на то, что свойство entities класса EntityContainer это массив классов Entity.
Причем для нас важно именно то, что это массив. В Swift Array это value type. А это значит, что во время его присваивания происходит копирование исходного массива.
Но и тут скрывается важный момент. Для стандартных типов коллекций (Array, Dictionary, Set) Swift использует оптимизацию под названием Copy-on-Write.
Это значит, что при присваивании копирования массива не происходит по умолчанию. Обе переменные пока используют одни и те же элементы. Копирование же происходит в момент изменения одной из переменных, т.е. именно тут:
1var entities = container.entities // Оптимизация COW, копирование массива еще не происходит2entities[0].id ="d"// При изменении происходит копирование массива. 3// Теперь entities и container.entities это две независимые копии.
var entities = container.entitiesentities[0].id = "d"
Теперь рассмотрим подробнее изменение элемента массива:
1entities[0].id ="d"
Да, массив ранее скопировался. Но давайте посмотрим, что в массиве. А в массиве класс Entity, который является reference type.
Это значит, что даже после копирования массива, его элементы ссылаются на одни и те же области памяти. Поэтому, когда мы поменяем id первого элемента массива entities, то мы поменяем и значение в массиве container.entities.
Далее рассмотрим строчки с удалением элемента из массива:
1var entity = entities.removeLast()// Удаляем последний элемент из массива entities2entity.id ="e"// Удаленный элемент все еще ссылается на ту же область памяти, что и последний элемент из container.entities
Метод массива removeLast() удаляет последний элемент и возвращает его. Этот удаленный элемент будет типа Entity, т.е. reference type. А мы не забываем, что в исходном массиве container.entities последний элемент все так же ссылается на эту же область памяти. Соответственно, изменения id удаленного элемента отразятся на исходном массиве.
Что и приводит нас к следующему ответу:
1print(container.entities)// d, b, e2print(entities)// d, b
Дополнительные вопросы
1. Потому что элементы внутри массива это классы, а значит reference type. Даже после копирования массива, новые элементы являются ссылками на те же области памяти.
2. Одним из рабочих способов будет определить функцию copy, которая будет создавать новые классы Entity с теми же значениями. Таким образом ссылки в разных массивах будут указывать на разные области памяти, поэтому изменения в одном массиве не будут затрагивать другой.