Найти в Дзене
Swift Explorer

Понимание Swift Copy-on-Write механизма

В Swift у нас есть ссылочные типы (классы) и типы значений (структуры, кортежи, перечисления). Типы значений имеют семантику копирования. Это означает, что если вы назначаете тип значения переменной или передаете его в качестве параметра функции (если это не параметр inout), базовые данные этого значения будут скопированы. У вас будет два значения с одинаковым содержимым, но выделенные в двух разных адресах памяти. Для более подробного объяснения разницы между типами ссылок и значений в блоге Apple или в нашей статье.

Поскольку мы будем говорить о Copy-on-Write, очень важно понимать семантику value в Swift.

Что это за копирование при записи?

В Swift, когда у вас есть большой тип значения и вам нужно назначить его другому объекту или передать функции в качестве параметра, копирование может быть очень дорогим с точки зрения производительности, потому как вам придется скопировать все базовые данные в другое место в памяти.

Чтобы свести к минимуму эту проблему, библиотека Swift реализует механизм copy on write для некоторых типов значений, таких как Array, где значение будет скопировано только после мутации, и то, только если оно имеет более одной ссылки на него, потому что если на это значение есть только одна ссылка, его не нужно копировать, его можно просто мутировать по ссылке. Таким образом, простое назначение переменной или передача в функцию не обязательно означает, что объект будет скопирован, и это действительно улучшает производительность.

Copy-on-Write не является поведением по умолчанию для всех типов значений, по дефолту это реализовано в стандартной библиотеке Swift только для определенных типов, таких как коллекции (String, Array, Set, Dictionary). Таким образом, это означает, что не каждый тип значений в Стандартной библиотеке имеет такое поведение. Кроме того, типы значений, которые вы создаете, не имеют его до тех пор пока вы не реализуете его самостоятельно. Как это сделать мы посмотрим позже в следующем разделе.

Давайте посмотрим на практике, как это работает с примером:

CopyOnWrite.swift

Это простой способ показать, как работает копирование при записи. Мы создали array1 со своими значениями и назначали array2 - array1 изначально никакого копирования не происходит, что видно на 12 и 13 строке. Затем когда мы меняем один из объектов, так как мы это сделали в 16 строке уже происходит полноценное копирование объекта.

Таким образом работает оптимизация которая позволяет не дублировать объекты без необходимости и не занимать лишнюю память.

Реализация поведения Copy-on-Write для ваших пользовательских типов значений

Со встроенным механизмом все понятно, мы можем им пользоваться и не заботиться о памяти, но что делать если у нас есть кастомные объекты не относящиеся к типам коллекций, однако весьма тяжелые - и мы при этом хотели бы обучить его такому же механизму чтобы лишний раз не насиловать память.

Такое вполне возможно:

Custom CoW.swift

Давайте попробуем разобраться что здесь происходит:

Мы создаем контейнер для хранения нашего объекта class Ref<T>, мы будем пользоваться классом потому как мы сможем отслеживать ссылается ли кто нибудь помимо основного объекта на этот контейнер

Далее мы создаем обертку для нашего объекта которая внутри имеет вычисляемое свойство var value: T и ссылку на наш контейнер Ref

В блоке get мы просто заберем значение которое лежит в контейнере, а в случае если мы захотим уже проводить какие то операции над нашим объектом то мы зайдем в блок set и там с помощью метода

func isKnownUniquelyReferenced<T>(_ object: inout T?) -> Bool where T : AnyObject

мы узнаем ссылается ли кто нибудь еще на наш Ref, и если это так то мы либо создадим абсолютно новый Ref, либо отдадим текущему новое значение. Что полностью соответствует механизму копирования при записи.

После того как мы присвоили var secondNumber = number мы можем проверить является контейнер Ref единым для них двоих и мы убеждаемся что это так, получается в двух объектах у нас есть ссылка на один и тот же value, но как только мы мутируем один из них, то мы можем увидеть что после такого действия оба объекта теперь ссылаются на свои уникальные контейнеры, внутри которых свои собственные абсолютно разные значения и таким образом мы полностью воспроизвели механизм Copy on Write.

Если вы дочитали статью до конца:

  1. поставьте лайк
  2. подпишитесь на канал

Это мотивирует писать новые статьи, а вы в свою очередь не упустите обновления!

В комментариях также можно оставить вопросы по статье или запросы на другие темы. Спасибо за прочтение!