Sobes.tech
Middle+

Дар бораи механизми оптимизатсияи нусхабардорӣ дар iOS нақл кунед.

sobes.tech AI

Ҷавоб аз AI

Механізм оптимізації копіювання в iOS і macOS, часто пов’язаний із використанням структури Copy-on-Write (CoW) або семантики значення, дозволяє уникнути надмірного копіювання даних при передачі структур або класів із семантикою значення, таких як масиви, словники, рядки і навіть власні структури, якщо вони реалізують протокол Equatable і Hashable (не строго, але типовий випадок).

Суть CoW:

  1. При створенні копії структури (наприклад, присвоєння однієї змінної іншій, передача у функцію за значенням), фактичне копіювання даних не відбувається. Обидві змінні вказують на одні й ті самі дані в пам’яті. Лічильник посилань на дані збільшується.
  2. Копіювання відкладається, доки одна з копій не спробує змінити дані.
  3. Лише при зміні даних вони реально копіюються, і змінюється вже нова копія. Початкова копія продовжує вказувати на початкові дані (якщо у неї залишився хоча б один власник).

Переваги:

  • Зниження накладних витрат на операції копіювання, особливо для великих обсягів даних (масивів, рядків).
  • Зменшення використання пам’яті, оскільки кілька сутностей можуть спільно використовувати одні й ті самі дані.
  • Підвищення продуктивності при передачі структур із семантикою значення.

Приклади, де CoW активно використовується:

  • NSArray, NSDictionary, NSString: В Objective-C ці класи використовують CoW для оптимізації.
  • Array, Dictionary, String у Swift: Ці базові типи у Swift мають семантику значення і використовують CoW.
var array1 = [1, 2, 3]
var array2 = array1 // Копіювання даних не відбувається, обидві змінні вказують на одні й ті самі дані

array2.append(4) // Відбувається реальне копіювання даних для array2, потім append
// Тепер array1 = [1, 2, 3] і array2 = [1, 2, 3, 4]

Недоліки:

  • Невеликі накладні витрати на перевірку необхідності копіювання при кожній зміні.
  • Можливість прихованого копіювання, яке може вплинути на продуктивність у несподіваних місцях.

Важливо пам’ятати, що CoW працює ефективно для структур із семантикою значення (struct, enum). Класи мають посилальну семантику, і при присвоєнні або передачі екземпляра класу копіюється лише посилання, а не самі дані об’єкта.

У Swift більшість вбудованих колекцій і рядків працюють із семантикою значення і використовують CoW під капотом. При створенні власних структур, що містять посилальні типи (наприклад, клас), CoW буде застосовуватися до самої структури, але не до вмісту посилального типу. Зміна властивості, що є посилальним типом всередині структури, не викличе CoW для всієї структури, але може вимагати CoW для самого посилального типу, якщо він його підтримує.

Для ручної реалізації CoW у власних типах даних у Swift можна використовувати комбінацію посилального типу для зберігання даних (наприклад, клас із внутрішньою структурою) і перевірки унікальності посилання перед зміною (наприклад, isKnownUniquelyReferenced).

class Container<T> {
    var value: T

    init(_ value: T) {
        self.value = value
    }
}

struct CustomStruct {
    private var _container: Container<[Int]>

    var data: [Int] {
        get { return _container.value }
        set {
            // Перевіряємо, чи є контейнер унікальною посиланням
            // Якщо ні, створюємо нову копію контейнера і даних
            if !isKnownUniquelyReferenced(&_container) {
                _container = Container(newValue)
            } else {
                // Якщо так, змінюємо дані безпосередньо у поточному контейнері
                _container.value = newValue
            }
        }
    }

    init(data: [Int]) {
        _container = Container(data)
    }
}

var s1 = CustomStruct(data: [1, 2, 3])
var s2 = s1 // CoW: _container у s1 і s2 зараз посилаються на один і той самий екземпляр Container

s2.data.append(4) // Викликається setter data.
                   // isKnownUniquelyReferenced для _container s2 буде false (оскільки s1 також на нього посилається).
                   // Створюється новий Container з копією [1, 2, 3, 4], s2._container тепер посилається на нього.
                   // s1._container все ще посилається на старий Container з [1, 2, 3].

print(s1.data) // Вивід: [1, 2, 3]
print(s2.data) // Вивід: [1, 2, 3, 4]