Sobes.tech
Middle+

Fale-me sobre o mecanismo de otimização de cópia no iOS.

sobes.tech IA

Resposta da IA

O mecanismo de otimização de cópia no iOS e macOS, frequentemente associado ao uso da estrutura Copy-on-Write (CoW) ou à semântica de valor, permite evitar cópias desnecessárias de dados ao passar estruturas ou classes com semântica de valor, como arrays, dicionários, strings e até estruturas personalizadas, se implementarem o protocolo Equatable e Hashable (não estritamente, mas em casos típicos).

A essência do CoW:

  1. Ao criar uma cópia de uma estrutura (por exemplo, atribuir uma variável a outra, passar por valor para uma função), não ocorre uma cópia real dos dados. Ambas as variáveis apontam para os mesmos dados na memória. O contador de referências aos dados aumenta.
  2. A cópia é adiada até que uma das cópias tente modificar os dados.
  3. Apenas no momento da modificação os dados são realmente copiados, e a modificação é feita na nova cópia. A cópia original continua a apontar para os dados iniciais (se ainda tiver pelo menos um proprietário).

Vantagens:

  • Redução de custos nas operações de cópia, especialmente para grandes volumes de dados (arrays, strings).
  • Diminuição do uso de memória, pois várias entidades podem compartilhar os mesmos dados.
  • Melhoria de desempenho ao passar estruturas com semântica de valor.

Exemplos onde o CoW é usado ativamente:

  • NSArray, NSDictionary, NSString: Em Objective-C, essas classes usam CoW para otimização.
  • Array, Dictionary, String em Swift: Esses tipos básicos em Swift têm semântica de valor e usam CoW.
var array1 = [1, 2, 3]
var array2 = array1 // Sem cópia de dados, ambas as variáveis apontam para os mesmos dados

array2.append(4) // Uma cópia real dos dados é feita para array2, depois append
// Agora, array1 = [1, 2, 3] e array2 = [1, 2, 3, 4]

Desvantagens:

  • Pequenos custos adicionais na verificação da necessidade de cópia a cada modificação.
  • Possibilidade de cópias ocultas, que podem afetar o desempenho em locais inesperados.

É importante lembrar que o CoW funciona eficientemente para estruturas com semântica de valor (struct, enum). Classes têm semântica de referência, e ao atribuir ou passar uma instância de classe, apenas a referência é copiada, não os dados do objeto.

No Swift, a maioria das coleções embutidas e strings funcionam com semântica de valor e usam CoW internamente. Ao criar estruturas próprias que contêm tipos de referência (por exemplo, uma classe), o CoW será aplicado à própria estrutura, mas não ao conteúdo do tipo de referência. Alterar uma propriedade de tipo de referência dentro de uma estrutura não acionará o CoW para toda a estrutura, mas pode exigir o CoW para o próprio tipo de referência, se ele o suportar.

Para implementar manualmente o CoW em tipos de dados próprios em Swift, pode-se usar uma combinação de um tipo de referência para armazenar dados (por exemplo, uma classe com uma estrutura interna) e verificar a unicidade da referência antes de modificar (por exemplo, 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 {
            // Verifica se o contêiner é referenciado de forma única
            // Se não, cria uma nova cópia do contêiner e dos dados
            if !isKnownUniquelyReferenced(&_container) {
                _container = Container(newValue)
            } else {
                // Se for, modifica os dados diretamente no contêiner atual
                _container.value = newValue
            }
        }
    }

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

var s1 = CustomStruct(data: [1, 2, 3])
var s2 = s1 // CoW: _container em s1 e s2 apontam para a mesma instância de Container

s2.data.append(4) // Setter de data é chamado.
                   // isKnownUniquelyReferenced para _container em s2 será false (pois s1 também faz referência a ele).
                   // Uma nova cópia do Container com [1, 2, 3, 4] é criada, e s2._container aponta para ela.
                   // s1._container ainda aponta para o Container antigo com [1, 2, 3].

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