Как работает copy-on-write в Swift на примере копирования массива и изменения элементов
Что проверяют
Можете ли вы объяснить механизм на конкретном примере, а не пересказать определение.
Как отвечать
Массив это структура, значимый тип, но данные хранит в буфере в куче. Присваивание копирует саму структуру, а буфер остаётся общим:
var a = [1, 2, 3]
var b = a // копирования данных нет, оба указывают на один буфер
b.append(4) // вот здесь буфер копируется, потому что владельцев двое
print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]Как система понимает, что пора копировать: перед изменением проверяется, единственный ли владелец буфера, через isKnownUniquelyReferenced. Владелец один, значит меняем на месте; владельцев несколько, значит сначала копируем.
Проверить это можно прямо в коде, посмотрев адрес буфера до и после изменения: у a он не поменяется, у b поменяется на первом изменяющем вызове.
Что из этого следует на практике:
- передача большого массива в функцию, которая его только читает, ничего не стоит;
- изменение массива, на который больше никто не ссылается, идёт без копирования, сколько бы элементов там ни было;
- а вот цикл, который в каждой итерации меняет элемент массива, пока где-то жива вторая ссылка, скопирует буфер один раз на первом изменении, дальше уже нет.
Тот же приём можно сделать в своём типе: положить данные в класс-хранилище и перед мутацией проверять уникальность ссылки.
Где спотыкаются
Считают, что копирование происходит в момент присваивания.
Думают, что copy-on-write работает и для классов. Там копировать нечего, это ссылка.
Не знают про isKnownUniquelyReferenced.
Уверены, что передача массива в функцию всегда дорогая, и городят inout без нужды.