Sobes.tech
Middle+

Vorbește-mi despre mecanismul de optimizare a copiatului în iOS.

sobes.tech AI

Răspuns de la AI

Mecanismul de optimizare a copiei în iOS și macOS, adesea asociat cu utilizarea structurii Copy-on-Write (CoW) sau a semnaticii valorii, permite evitarea copiei inutile a datelor atunci când se transmit structuri sau clase cu semnatică de valoare, precum array-uri, dicționare, șiruri și chiar structuri proprii, dacă implementează protocolul Equatable și Hashable (nu strict, dar cazul tipic).

Esenta CoW:

  1. La crearea unei copii a unei structuri (de exemplu, atribuirea unei variabile alteia, sau transmiterea într-o funcție prin valoare), nu are loc copierea efectivă a datelor. Ambele variabile indică aceleași date în memorie. Contorul de referințe pentru date crește.
  2. Copierea este amânată până când una dintre copii încearcă să modifice datele.
  3. Doar în momentul modificării, datele sunt copiate efectiv, iar copia modificată. Copia originală continuă să indice către datele inițiale (atât timp cât are cel puțin un proprietar).

Avantaje:

  • Reducerea costurilor de suprasarcină pentru operațiile de copiere, mai ales pentru volume mari de date (array-uri, șiruri).
  • Reducerea utilizării memoriei, deoarece mai multe entități pot folosi în comun aceleași date.
  • Creșterea performanței la transmiterea structurilor cu semnatică de valoare.

Exemple în care CoW este utilizat activ:

  • NSArray, NSDictionary, NSString: În Objective-C, aceste clase folosesc CoW pentru optimizare.
  • Array, Dictionary, String în Swift: Aceste tipuri de bază în Swift au semnatică de valoare și utilizează CoW.
var array1 = [1, 2, 3]
var array2 = array1 // Nu are loc copierea, ambele variabile indică aceleași date

array2.append(4) // Are loc copierea efectivă a datelor pentru array2, apoi append
// Acum array1 = [1, 2, 3] și array2 = [1, 2, 3, 4]

Dezavantaje:

  • Costuri mici de suprasarcină pentru verificarea necesității de copiere la fiecare modificare.
  • Posibilitatea unei copieri ascunse, care poate afecta performanța în locuri neașteptate.

Este important să rețineți că CoW funcționează eficient pentru structuri cu semnatică de valoare (struct, enum). Clasele au semnatică de referință, iar la atribuirea sau transmiterea unei instanțe de clasă, se copiază doar referința, nu datele propriu-zise ale obiectului.

În Swift, majoritatea colecțiilor încorporate și stringurile funcționează cu semnatică de valoare și utilizează CoW în spate. La crearea propriilor structuri care conțin tipuri de referință (de exemplu, o clasă), CoW va fi aplicat structurii în sine, dar nu conținutului tipului de referință. Modificarea unei proprietăți referențiale din interiorul structurii nu va declanșa CoW pentru întreaga structură, dar poate necesita CoW pentru tipul de referință în sine, dacă acesta o suportă.

Pentru implementarea manuală a CoW în propriile tipuri de date în Swift, se poate folosi o combinație între un tip de referință pentru stocarea datelor (de exemplu, o clasă cu o structură internă) și verificarea unicitatea referinței înainte de modificare (de exemplu, 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 {
            // Verificăm dacă containerul este referențiat în mod unic
            // Dacă nu, creăm o copie nouă a containerului și datelor
            if !isKnownUniquelyReferenced(&_container) {
                _container = Container(newValue)
            } else {
                // Dacă da, modificăm datele direct în containerul curent
                _container.value = newValue
            }
        }
    }

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

var s1 = CustomStruct(data: [1, 2, 3])
var s2 = s1 // CoW: _container în s1 și s2 indică acum același Container

s2.data.append(4) // Se apelează setter-ul pentru data.
                   // isKnownUniquelyReferenced pentru _container în s2 va fi false (deoarece și s1 îl referențiază).
                   // Se creează un nou Container cu o copie a [1, 2, 3, 4], s2._container indicând acum către acesta.
                   // s1._container încă indică către vechiul Container cu [1, 2, 3].

print(s1.data) // Output: [1, 2, 3]
print(s2.data) // Output: [1, 2, 3, 4]