Как работает copy-on-write для строк в Swift?

Ответ

Copy-on-write (CoW) — это оптимизация, при которой копирование данных откладывается до момента фактической модификации. В Swift строки (тип String) реализуют этот механизм.

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

  1. При присваивании строки другой переменной создается только новая ссылка на те же данные
  2. Фактическое копирование буфера памяти происходит только при попытке изменить одну из копий

Пример:

var original = "Hello, World!"
var copy = original // Нет копирования, обе переменные ссылаются на один буфер

print(original == copy) // true

// Проверим, что это одна и та же память (условно)
print(CFGetRetainCount(original as CFString)) // Увеличится счетчик ссылок

copy.append("!") // ТЕПЕРЬ происходит копирование!
// original и copy теперь имеют разные буферы в памяти

print(original) // "Hello, World!"
print(copy)     // "Hello, World!!"

Реализация CoW для пользовательских структур:

struct MyCoWStruct {
    private class Ref<T> {
        var value: T
        init(_ value: T) { self.value = value }
    }

    private var ref: Ref<[Int]>

    init(values: [Int]) {
        ref = Ref(values)
    }

    var values: [Int] {
        get { return ref.value }
        set {
            // Проверяем, единственная ли это ссылка
            if !isKnownUniquelyReferenced(&ref) {
                ref = Ref(newValue) // Копируем при модификации
                return
            }
            ref.value = newValue
        }
    }
}

var a = MyCoWStruct(values: [1, 2, 3])
var b = a // Нет копирования
b.values.append(4) // Происходит копирование, так как b изменяется

Важные особенности:

  • CoW работает только для типов-значений (struct, enum)
  • Компилятор Swift автоматически применяет CoW для стандартных коллекций (Array, Dictionary, Set) и строк
  • Для кастомных структур нужно реализовывать CoW вручную
  • Механизм не гарантирован для очень маленьких структур, где копирование дешевле проверки уникальности ссылки

Преимущества:

  • Экономия памяти при частом копировании
  • Быстрое присваивание без накладных расходов
  • Прозрачность для разработчика (работает автоматически)

Ответ 18+ 🔞

Слушай, а вот эта ваша штука, Copy-on-Write, она же просто гениальная, блядь! Ну представь себе: ты пишешь код, и тебе надо скопировать здоровенный массив или строку длиной в овердохуища символов. Обычно система тупо выделяет новую память и копирует всё побайтово — ну это ж пиздец как долго и ресурсов жрёт!

А тут, блядь, Swift подходит с хитрой жопой и говорит: «А зачем копировать-то сразу, ёпта? Давай схитрим!». И вот как это работает, внатуре:

Когда ты пишешь var copy = original, нихуя не копируется! Вообще! Создаётся просто новая бумажка-ярлык, которая тычет пальцем в ту же самую кучу данных в памяти. Как два соседа, которые смотрят один и тот же телевизор через стенку. Пока все только смотрят — всё ок.

var original = "Привет, мир, блядь!"
var copy = original // Тишина. Ничего не происходит. Обе переменные показывают на одно и то же.

print(original == copy) // true, ебать

Но вот один из соседей, долбоёб, решил переключить канал! Вот тут-то и начинается магия. Система хватает его за руку: «Стоять, мудила! Ты же не один!». И только в этот момент, блядь, происходит настоящее копирование данных в новое место, и уже потом этот чувак меняет свою личную копию.

copy.append("!!!") // БАБАХ! Вот ТЕПЕРЬ, сука, память скопировали!
// original и copy теперь живут отдельно, как разведённые супруги.

print(original) // "Привет, мир, блядь!"
print(copy)     // "Привет, мир, блядь!!!"

А если хочешь свою структуру такую же хитрожопую сделать, то придётся немного попотеть. Надо завернуть данные в класс (потому что классы — ссылочные) и при каждой попытке что-то поменять проверять: «А я один тут хозяин этой ссылки, или кто-то ещё подглядывает?».

struct MyCoWStruct {
    private class Ref<T> { // Обёртка-затычка, как буфер
        var value: T
        init(_ value: T) { self.value = value }
    }

    private var ref: Ref<[Int]> // Сами данные прячем сюда

    init(values: [Int]) {
        ref = Ref(values)
    }

    var values: [Int] {
        get { return ref.value } // Читать можно всегда
        set {
            // А вот тут магия! Проверяем, не шпионят ли за нами?
            if !isKnownUniquelyReferenced(&ref) {
                // О, бля! Кто-то ещё есть! Копируемся срочно!
                ref = Ref(newValue)
                return
            }
            // Уф, мы одни. Можно менять на месте.
            ref.value = newValue
        }
    }
}

var a = MyCoWStruct(values: [1, 2, 3])
var b = a // Опять тишина. Копирования нет.
b.values.append(4) // А вот тут — есть! Потому что b попытался изменить общие данные.

И главное, что стандартные штуки в Swift — Array, String, Dictionary — уже всё это умеют из коробки, блядь! Ты даже не задумываешься. Пишешь код, а они там под капотом сами решают, когда копировать, а когда просто ссылку подсунуть. Умно, ёпта!

Но есть нюанс, конечно. Если твоя структура состоит из трёх Int-ов, то никакой CoW не нужен — скопировать их дешевле, чем городить всю эту проверочную систему с классами и ссылками. Это как вызывать вертолёт, чтобы доехать до соседнего подъезда — проще пешком дойти, в рот меня чих-пых!

В общем, штука мощная. Экономит память, экономит время. Swift-разработчики — хитрожопые ребята, ничего не скажешь.