Как реализовать семантику Copy-on-Write (CoW) для пользовательского типа в Swift?

Ответ

Copy-on-Write (CoW) оптимизирует работу со value-типами, откладывая копирование данных до момента их реальной модификации. Реализуется через комбинацию struct и внутреннего ссылочного типа (class).

Базовая реализация:

// 1. Класс-хранилище (ссылочный тип)
final class Ref<T> {
    var value: T
    init(_ value: T) { self.value = value }
}

// 2. Публичная структура (value-тип) с CoW
struct CoWBox<T> {
    private var ref: Ref<T>

    init(_ value: T) {
        ref = Ref(value) // Создаем единственную ссылку
    }

    var value: T {
        get { ref.value } // Чтение — используем общие данные
        set {
            // Ключевой момент: копируем только если ссылка не уникальна
            if !isKnownUniquelyReferenced(&ref) {
                ref = Ref(newValue) // Создаем новое хранилище
                return
            }
            // Если ссылка уникальна — модифицируем существующие данные
            ref.value = newValue
        }
    }
}

Как это работает:

  1. CoWBox — это структура. При присваивании (let box2 = box1) копируется только ссылка ref, а не сами данные.
  2. При чтении value данные используются напрямую из общего хранилища.
  3. При записи в value проверяется функция isKnownUniquelyReferenced(&ref). Она возвращает true, если на объект Ref есть только одна ссылка (этот экземпляр CoWBox — его единственный владелец).
    • Если true — можно безопасно менять данные «на месте».
    • Если false — значит, данные разделяют другие экземпляры CoWBox. Создается новый объект Ref с новым значением, и дальнейшие изменения не затронут другие копии.

Где используется: Именно так реализованы стандартные коллекции Swift (Array, Dictionary, Set, String), что делает их эффективными при передаче по значению.

Ответ 18+ 🔞

Да ты посмотри, какая хитрая жопа этот Copy-on-Write! Ну реально, гениальная, блядь, оптимизация для структур. Суть-то проще пареной репы, если не загоняться.

Вот представь: у тебя есть здоровенный массив, и ты его в десять функций передаёшь. Если бы он каждый раз копировался — пипец, производительность накрылась бы медным тазом. А CoW говорит: «Да похуй, чувак, копируй смело!». Но копирует он не данные, а только... ссылочку! Ёпта, как так?

Вот как это под капотом устроено, блядь:

// 1. Класс-хранилище (ссылочный тип)
final class Ref<T> {
    var value: T
    init(_ value: T) { self.value = value }
}

// 2. Публичная структура (value-тип) с CoW
struct CoWBox<T> {
    private var ref: Ref<T>

    init(_ value: T) {
        ref = Ref(value) // Создаем единственную ссылку
    }

    var value: T {
        get { ref.value } // Чтение — используем общие данные
        set {
            // Ключевой момент: копируем только если ссылка не уникальна
            if !isKnownUniquelyReferenced(&ref) {
                ref = Ref(newValue) // Создаем новое хранилище
                return
            }
            // Если ссылка уникальна — модифицируем существующие данные
            ref.value = newValue
        }
    }
}

Алгоритм работы — волнение ебать:

  1. Создаёшь CoWBox — внутри рождается одинокий объект Ref с данными.
  2. Копируешь структуру (let второйЯщик = первыйЯщик). Что скопировалось? Да нихуя! Только ссылка ref на тот же самый внутренний класс. Данные-то одни на двоих!
  3. Читаешь значение — всё ок, берётся из общего хранилища. Все довольны.
  4. А вот когда пытаешься записать — тут начинается магия. Вызывается эта хитрая функция isKnownUniquelyReferenced(&ref). Она, сука, как сторожевой пёс, проверяет: «А не делим ли мы эти данные с кем-то ещё?».
    • Если ссылка уникальна (никто больше на неё не смотрит) — да ради бога, меняй данные на месте! Кто тебе запретит?
    • Если ссылка НЕ уникальна (ой, а там уже второйЯщик подглядывает) — тут-то и срабатывает «copy» из названия! Создаётся новый объект Ref с новыми данными, и уже в него идёт запись. Старые данные остаются нетронутыми для других пользователей. Красота, блядь!

Где это, спрашивается, применяется? Да везде, сука! Все стандартные коллекции Swift — Array, Dictionary, Set — они именно так и работают. Выглядят как value-типы, ведут себя как value-типы, но пока их не трогаешь — они все сидят на одной общей куче данных, как мартышлюшки. А тронул — получи свою личную копию. Удобно, эффективно, в рот меня чих-пых!