← Назад к вопросам

Как переопределить Copy-on-write?

2.7 Senior🔥 71 комментариев
#Управление памятью#Язык Swift

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Copy-on-Write (CoW) в Swift

Copy-on-Write — это оптимизация производительности, которая уменьшает копирование данных в value type'ах. Swift автоматически использует CoW для стандартных коллекций (Array, Dictionary, String), но можно реализовать это вручную для custom структур.

Как работает автоматический CoW

Swift коллекции используют внутренний буфер (reference type), который делится между копиями. Когда вы модифицируете коллекцию, Swift проверяет, есть ли другие владельцы. Если есть — делает копию перед модификацией.

var array1 = [1, 2, 3]
var array2 = array1  // Буфер делится, не копируется

array2.append(4)  // Здесь Swift делает копию перед изменением
// array1 = [1, 2, 3] (не изменился)
// array2 = [1, 2, 3, 4]

Реализуем CoW для пользовательского типа

Для контролируемого CoW нужно:

  1. Обёрнуть данные в class (reference type)
  2. Проверять количество ссылок перед модификацией
  3. Делать копию если нужно
class PhotoStorage {
    var pixels: [UInt32]
    
    init(pixels: [UInt32]) {
        self.pixels = pixels
    }
    
    // Помечаем как mutating, чтобы можно было изменять
    func copy() -> PhotoStorage {
        return PhotoStorage(pixels: pixels)
    }
}

struct Photo {
    private var storage: PhotoStorage
    
    init(pixels: [UInt32]) {
        storage = PhotoStorage(pixels: pixels)
    }
    
    // Пример 1: Manual isKnownUniquelyReferenced
    mutating func setPixel(at index: Int, to value: UInt32) {
        // Проверяем, уникальна ли ссылка
        if !isKnownUniquelyReferenced(&storage) {
            // Другие владельцы есть, делаем копию
            storage = storage.copy()
        }
        storage.pixels[index] = value
    }
    
    // Пример 2: Более элегантный способ с методом
    mutating func ensureUniqueStorage() {
        if !isKnownUniquelyReferenced(&storage) {
            storage = storage.copy()
        }
    }
}

// Использование
var photo1 = Photo(pixels: Array(repeating: 0, count: 100))
var photo2 = photo1  // Хранилище делится

photo2.setPixel(at: 0, to: 255)  // Копия делается только здесь

Более сложный пример с computed properties

struct Image {
    private var storage: ImageStorage
    
    mutating func getPixels() -> inout [UInt32] {
        ensureUniqueStorage()
        return &storage.pixels
    }
    
    private mutating func ensureUniqueStorage() {
        if !isKnownUniquelyReferenced(&storage) {
            storage = storage.copy()
        }
    }
}

Проблемы переопределения CoW

Проблема 1: isKnownUniquelyReferenced() может быть неточной

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

func process(_ photo: inout Photo) {
    // Здесь isKnownUniquelyReferenced может вернуть true
    // даже если это параметр функции
    photo.setPixel(at: 0, to: 100)
}

Проблема 2: Performance overhead

Проверка уникальности имеет стоимость. Для часто вызываемых операций может быть медленнее.

Проблема 3: Thread safety

class storage не thread-safe по умолчанию. Если нужна многопоточность, требуется дополнительная синхронизация.

Когда переопределять CoW

Переопределяйте CoW когда:

  • Работаете с большими структурами данных
  • Часто копируете, но редко модифицируете
  • Хотите fine-grained контроль над копированием
  • Нужна специфическая семантика для вашего типа

Альтернатива: @dynamicMemberLookup

Можно также использовать более элегантный подход с wrapper'ом:

@propertyWrapper
struct CopyOnWrite<T: AnyObject> {
    private var object: T
    
    var wrappedValue: T {
        mutating get {
            if !isKnownUniquelyReferenced(&object) {
                // Копируем при чтении
                object = object.copy() as! T
            }
            return object
        }
    }
}
Как переопределить Copy-on-write? | PrepBro