DEV Community

Cover image for Value Types vs Reference Types em Swift: Quando Usar Struct ou Class?
Lys
Lys

Posted on

Value Types vs Reference Types em Swift: Quando Usar Struct ou Class?

O questionamento sobre a diferença entre Struct e Class é muito importante, te ajuda a responder uma pergunta recorrente em entrevistas técnicas, e também te ajuda a entender um pouco mais do que tanto usamos no dia a dia.

Pra você ter uma ideia, tipos que usamos no dia a dia são Struct's, como String, Int, Array, Date, etc.

Com o SwiftUI, vem um aumento do uso de Struct's, ja que para criamos View Controller's, criamos usando Class, e para criar View's em SwiftUI, usamos Struct.

Há uma lista de coisas que ambas tem em comum:

  • Definem propriedades
  • Definem métodos
  • Podem usar subscripts
  • Possuem inicializadores
  • Podem ser estendidas
  • Podem adotar protocolos

Mesmo muito semelhantes, há diferenças cruciais entre elas que podem ser muito importantes na hora de tomar uma decisão criando algo com Struct ou Class

  • Struct é Value Type e Class é Reference Type

Sendo um tipo de valor, ao atribuí-la a uma nova variável, uma cópia independente é criada. Alterações em uma não afetam a outra.

Classes são tipos de referência, então atribuições compartilham a mesma instância. Alterar uma afeta todas as referências.

Ao criar um objeto Store, sendo uma struct, e um objeto Bank, sendo uma class.

import UIKit

struct Store {
    var salesQuantity: Int
}

class Bank {
    var accountNumber: Int

    init(accountNumber: Int) {
        self.accountNumber = accountNumber
    }
}

Enter fullscreen mode Exit fullscreen mode

Vamos instanciar cada um deles e atribuir um valor inicial para salesQuantity e accountNumber.

Ao criar foodStore, por ser uma struct, uma cópia de clothesStore é criada e ao atribuirmos um novo valor, apenas a cópia é modificada.

// Criando instâncias
let clothesStore = Store(salesQuantity: 100)
var foodStore = clothesStore  // Isso cria uma CÓPIA
foodStore.salesQuantity = 50  // Modifica apenas a cópia
Enter fullscreen mode Exit fullscreen mode

Ao criar grayBank, por ser uma class, uma referência de pinkBank é criada e ao atribuirmos um novo valor, modificamos o objeto original.

// Criando instâncias
let pinkBank = Bank(accountNumber: 123456789)
var grayBank = pinkBank  // Isso cria uma REFERÊNCIA (mesmo objeto)
grayBank.accountNumber = 987654321  // Modifica o objeto original
Enter fullscreen mode Exit fullscreen mode

Conseguimos visualizar isso ao printar os valores de cada um.

// Imprimindo valores
print("\nValores das classes:")
print("pinkBank:", pinkBank.accountNumber)
print("grayBank:", grayBank.accountNumber)

print("\nValores das structs:")
print("clothesStore:", clothesStore.salesQuantity)
print("foodStore:", foodStore.salesQuantity)
Enter fullscreen mode Exit fullscreen mode

Print de resultado

  • Herança

Classes podem herdar de uma única superclasse, ou de uma classe, que herda de outra, formando uma cadeia de herança (não há herança múltipla direta de várias superclasses) e protocolos.
Struct's não herdam de classes, de struct's, não fazem cadeia de herança, apenas herdam de protocolos.

  • Memória

Classes usam o ARC para contagem automática de referência.
A classe também tem desinicializadores que permitem que uma instância de classe libere quaisquer recursos que tenha alocado.
Struct's não usam ARC para gerenciamento de memória, pequenas structs geralmente são armazenadas na stack (pilha) e struct's maiores ou contendo referências podem acabar sendo armazenadas no heap.

🏃‍♀️ Diferenciais na performance 🏃‍♀️

Classes

  • Alocadas no heap, que tem maior custo de gerenciamento
  • Requerem contagem de referência (ARC overhead)
  • Acesso indireto via ponteiro (pode causar cache misses)
  • Mesmo para objetos pequenos, há overhead de metadados

Struct's

  • Alocadas na stack (pilha), que é mais rápida para alocação/desalocação
  • Acesso direto à memória (menos indireção)
  • Cópias são baratas para tipos pequenos (até alguns campos)
  • Melhor localidade de cache (dados contíguos na memória)

Também falando de thread safety

Como são copiadas ao serem passadas no código, as structs podem ser usadas com segurança em ambientes multi-thread. As classes, por trabalharem com referências, demandam sincronizações cuidadosas quando acessadas por múltiplas threads, para prevenir condições de corrida.

📖 O que a documentação diz? 📖

Usar Class quando

  • Você precisa de herança
  • É necessário compartilhar um estado mutável entre várias partes do código, ou seja, múltiplas referências devem apontar para a mesma instância.
  • O tipo precisa de recursos como contagem de referência (ARC) para gerenciamento de memória.
  • O tipo precisa de recursos como desinicializadores .
  • O tipo precisa interoperar com APIs baseadas em classes (como muitos frameworks da Apple: UIKit, AppKit etc.).

Usar Struct quando

  • Você não precisa de herança nem de desinicializadores.
  • O tipo será copiado em vez de compartilhado
  • As propriedades armazenadas são todas também valores (ou seja, tipos que não têm semântica de referência).
  • O desempenho importa, e o tipo é pequeno e leve o suficiente para ser manipulado eficientemente com cópia.
  • Você quer imutabilidade por padrão (ao usar let, as structs inteiras são imutáveis, ao contrário de classes).

Top comments (0)