Swift: Classes e Structs do Swift
Classes e structs são os dois tipos principais para construir modelos de dados no Swift. Eles compartilham muitas semelhanças, mas diferem fundamentalmente no gerenciamento de memória e em como os valores são passados. Esta lição ajuda a esclarecer a estratégia de seleção para cada um.
1. O Que Você Vai Aprender
- Sintaxe básica para definir classes e structs
- Diferenças principais entre tipos por valor (struct) e tipos por referência (classe)
- O propósito da palavra-chave
mutating - Regras de inicialização para classes e structs
- Quando usar uma classe vs uma struct
2. Uma História Real de uma Desenvolvedora iOS
(1) Problema: Alterar uma Conta de Usuário em um Lugar Afeta Outro
Alice escolheu classe para modelar uma conta de usuário:
class UserAccount {
var name: String
var balance: Double
init(name: String, balance: Double) {
self.name = name
self.balance = balance
}
}
let account1 = UserAccount(name: "Alice", balance: 1000)
let account2 = account1 // References the same instance
account2.name = "Bob"
print(account1.name) // "Bob" — account1 was also changed!
Alice só queria criar um novo usuário, mas acidentalmente alterou os dados originais. Os efeitos colaterais dos tipos por referência se propagaram por toda a base de código sem que ela percebesse.
(2) Solução: Struct como Tipo por Valor
struct UserAccount {
var name: String
var balance: Double
}
var account1 = UserAccount(name: "Alice", balance: 1000)
var account2 = account1 // Copies an independent set of data
account2.name = "Bob"
print(account1.name) // "Alice" — account1 is unaffected
A atribuição de struct realiza uma cópia de valor — cada instância é independente.
(3) Benefício: Comportamento de Dados Previsível
| Dimensão | classe (Tipo por Referência) | struct (Tipo por Valor) |
|---|---|---|
| Comportamento de atribuição | Compartilha a mesma instância | Cópia independente |
| Risco de mutação acidental | Alto (aliasing) | Nenhum |
| Alocação de memória | Alocação no heap | Alocação na pilha (mais eficiente) |
| Segurança de thread | Requer sincronização manual | Naturalmente isolada |
| Adequado para | Estado mutável compartilhado | Modelos de dados, semântica de valor |
3. Definindo Classes e Structs
(1) Comparação de Sintaxe Básica
graph TB
subgraph classe
A["class Pessoa {"]
B[" var nome: String"]
C[" var idade: Int"]
D[" func saudar() { }"]
E["}"]
end
subgraph struct
F["struct Pessoa {"]
G[" var nome: String"]
H[" var idade: Int"]
I[" func saudar() { }"]
J["}"]
end
| Característica | classe | struct | Observações |
|---|---|---|---|
| Propriedades | Sim | Sim | Propriedades armazenadas e computadas |
| Métodos | Sim | Sim | Métodos de instância e de tipo |
| Inicializadores | Sim | Sim | classe não gera automaticamente init por membro |
| Herança | Sim | Não | struct não pode herdar |
| Desinicializador | Sim | Não | struct não tem deinit |
| Contagem de referência | Sim | Não | struct não precisa de ARC |
| Conversão de tipo | Sim | Não | as?/as! apenas para classe |
▶ Exemplo: Definindo Classes e Structs
// ============================================
// Class and struct definition comparison
// ============================================
// Class definition — must write init manually
class PersonClass {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func introduce() {
print("Hi, I'm \(name), \(age) years old")
}
}
// Struct definition — automatically gets a memberwise initializer
struct PersonStruct {
var name: String
var age: Int
func introduce() {
print("Hi, I'm \(name), \(age) years old")
}
}
let p1 = PersonClass(name: "Alice", age: 28)
p1.introduce()
let p2 = PersonStruct(name: "Bob", age: 22) // Auto-generated init
p2.introduce()
Saída:
TEXT 📖 Somente leituraHi, I'm Alice, 28 years old Hi, I'm Bob, 22 years old
4. Tipos por Valor vs Tipos por Referência
(1) Diferenças Principais
graph LR
subgraph "Tipo por Valor (struct)"
A["var a = Ponto(x: 1, y: 2)"]
B["var b = a"]
C["b.x = 5"]
D["a.x → 1 (independente)"]
A --> B --> C --> D
end
subgraph "Tipo por Referência (classe)"
E["var a = Ponto(x: 1, y: 2)"]
F["var b = a"]
G["b.x = 5"]
H["a.x → 5 (compartilhado)"]
E --> F --> G --> H
end
| Dimensão | Tipo por Valor (struct) | Tipo por Referência (classe) |
|---|---|---|
| Semântica de atribuição | Copia conteúdo | Copia referência |
| Verificação de igualdade | == compara valores |
=== compara identidade |
| Local de armazenamento | Pilha (mais rápido) | Heap (gerenciado por ARC) |
| Constante let | Propriedades são imutáveis | Referência é imutável mas propriedades podem mudar |
| Parâmetros de função | Cópia por padrão | Compartilha referência por padrão |
▶ Exemplo: Comparação de Comportamento de Atribuição
// ============================================
// Value type vs reference type assignment behavior
// ============================================
// Struct — value type
struct Position {
var x: Int
var y: Int
}
var posA = Position(x: 10, y: 20)
var posB = posA
posB.x = 99
print("posA: (\(posA.x), \(posA.y))") // Unchanged
print("posB: (\(posB.x), \(posB.y))") // Only affects posB
// Class — reference type
class Location {
var x: Int
var y: Int
init(x: Int, y: Int) { self.x = x; self.y = y }
}
var locA = Location(x: 10, y: 20)
var locB = locA
locB.x = 99
print("locA: (\(locA.x), \(locA.y))") // Also changed!
print("locB: (\(locB.x), \(locB.y))")
Saída:
TEXT 📖 Somente leituraposA: (10, 20) posB: (99, 20) locA: (99, 20) locB: (99, 20)
(2) O Operador de Identidade ===
Tipos por referência podem usar === para verificar se duas variáveis apontam para a mesma instância:
let loc1 = Location(x: 5, y: 10)
let loc2 = loc1
let loc3 = Location(x: 5, y: 10) // Different instance
print(loc1 === loc2) // true — same instance
print(loc1 === loc3) // false — different instances
Dica:
===é o operador de identidade, distinto de==(o operador de igualdade). Tipos por valor só podem usar==(devem implementar Equatable) e não podem usar===.
5. Métodos mutating e Estratégia de Seleção
(1) A Palavra-chave mutating
Os métodos de instância de struct não podem modificar propriedades por padrão. Para fazê-lo, eles devem ser marcados como mutating:
| Tipo de Método | classe | struct (não-mutating) | struct (mutating) |
|---|---|---|---|
| Modificar propriedade | Permitido | Erro de compilação | Permitido |
| Modificar self | Permitido | Não permitido | Permitido |
| Requisito de chamada | Sem restrição | Pode ser chamado em constante let | Deve ser chamado em var |
▶ Exemplo: Métodos mutating
// ============================================
// Struct mutating methods
// ============================================
struct Counter {
var count = 0
// Non-mutating — read-only method
func display() {
print("Count: \(count)")
}
// mutating — modifies property
mutating func increment() {
count += 1
}
// mutating — replaces the entire instance
mutating func reset() {
self = Counter() // Replace self
}
}
var counter = Counter() // Must be var to call mutating methods
counter.increment()
counter.increment()
counter.display() // Count: 2
counter.reset()
counter.display() // Count: 0
Saída:
TEXT 📖 Somente leituraCount: 2 Count: 0
(2) Estratégia de Seleção
| Cenário | Recomendado | Motivo |
|---|---|---|
| Modelos de dados (Ponto, Tamanho, Usuario) | struct | Semântica de valor, seguro e previsível |
| Precisa de herança (subclasse UIView, BaseViewController) | classe | Herança é exclusiva de classe |
| Precisa de estado mutável compartilhado | classe | Tipos por referência compartilham automaticamente |
| Precisa de interoperabilidade com ObjC | classe | @objc requer classe |
| Wrapper leve (envolvendo poucos valores) | struct | Alocação na pilha, zero sobrecarga |
| Precisa de limpeza com deinit | classe | struct não tem deinit |
▶ Exemplo: Struct como Escolha Padrão para Modelos
// ============================================
// Why modern Swift prefers struct for models
// ============================================
struct User {
let id: Int
var name: String
var email: String
var score: Int
mutating func addScore(_ points: Int) {
score += points
}
}
var user = User(id: 1, name: "Alice", email: "alice@example.com", score: 100)
user.addScore(50)
// Copy-and-mutate pattern — safely create changed versions
let admin = User(id: 2, name: "Admin", email: "admin@example.com", score: 0)
// admin.addScore(10) // Compile error! let constant cannot call mutating methods
var editor = admin
editor.name = "Editor" // Independent modification, doesn't affect admin
Saída:
TEXT 📖 Somente leitura(No console output — demonstrates value semantics: editor.name = "Editor" does not affect admin.name)
6. Exemplo Completo: Sistema de Conta Bancária
// ============================================
// Complete example: Bank account system
// Features: class for account management, struct for transaction records
// ============================================
import Foundation
// 1. Transaction record — value type struct
struct Transaction {
let id: Int
let amount: Double
let type: String // "deposit" or "withdraw"
let date: Date
func description() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return "[\(formatter.string(from: date))] \(type): $\(amount)"
}
}
// 2. Account — reference type class (shared state)
class BankAccount {
let accountNumber: String
var balance: Double
private var transactions: [Transaction] = []
private var nextId = 1
init(accountNumber: String, initialBalance: Double = 0) {
self.accountNumber = accountNumber
self.balance = initialBalance
}
func deposit(amount: Double) {
guard amount > 0 else { return }
balance += amount
let tx = Transaction(id: nextId, amount: amount, type: "deposit", date: Date())
transactions.append(tx)
nextId += 1
}
func withdraw(amount: Double) -> Bool {
guard amount > 0 && amount <= balance else { return false }
balance -= amount
let tx = Transaction(id: nextId, amount: amount, type: "withdraw", date: Date())
transactions.append(tx)
nextId += 1
return true
}
func printStatement() {
print("=== Account \(accountNumber) ===")
print("Balance: $\(balance)")
print("Transactions:")
for tx in transactions {
print(" \(tx.description())")
}
}
}
// 3. Usage
let account = BankAccount(accountNumber: "ACC-001", initialBalance: 1000)
account.deposit(amount: 500)
account.withdraw(amount: 200)
account.deposit(amount: 100)
account.printStatement()
Saída:
TEXT 📖 Somente leitura=== Account ACC-001 === Balance: $1400.0 Transactions: [2026-07-30] deposit: $500.0 [2026-07-30] withdraw: $200.0 [2026-07-30] deposit: $100.0
❓ Perguntas Frequentes
P: Por que a Apple recomenda preferir struct? R: Tipos por valor são mais seguros e previsíveis. Sem efeitos colaterais de referências compartilhadas, sem ciclos de retenção e sem sobrecarga de contagem de referência. Tipos da biblioteca padrão do Swift como String, Array e Dictionary são todos structs. P: struct tem vantagem de desempenho? R: Sim. Structs são alocadas na pilha, sem sobrecarga de contagem de referência e sem custo de alocação no heap. No entanto, passar structs grandes entre funções pode incorrer em custos de cópia mais altos comparado a passar uma referência. O mecanismo Copy-on-Write do Swift otimiza isso. P: Devo sempre escrever init para uma classe manualmente? R: Para uma classe, se todas as propriedades tiverem valores padrão e nenhum inicializador for definido, o Swift gera um init padrão sem argumentos. Mas ele nunca gera automaticamente um init por membro como a struct faz. P: struct pode ter propriedades lazy? R: Sim.
lazy varfunciona em structs também, mas como propriedades lazy não podem ser acessadas em structs imutáveis, o uso prático é limitado. lazy é mais natural em classes. P: mutating pode ser usado em classes? R: Não.mutatingsó se aplica a tipos por valor (struct e enum). Métodos de classe podem modificar propriedades por padrão — sem necessidade da palavra-chavemutating.
📖 Resumo
- Tanto classes quanto structs podem definir propriedades, métodos, subscritos e extensões
- Structs são tipos por valor (cópia na atribuição); classes são tipos por referência (copia a referência)
- Structs obtêm um inicializador por membro automaticamente; classes devem escrever init manualmente
- A palavra-chave
mutatingpermite que métodos de struct modifiquem suas próprias propriedades - Use
===para verificar se duas variáveis de tipo por referência apontam para a mesma instância - Prefira struct; use classe quando precisar de herança ou estado mutável compartilhado
📝 Exercícios
- Básico: Defina uma struct
Livrocom propriedadestitulo,autor,paginase um métodoresumo(). Crie duas instâncias e teste o comportamento de cópia de tipo por valor. - Intermediário: Defina uma classe
SensorTemperaturacom uma propriedadetempAtuale um array de históricoleituras: [Double]. Implemente os métodosregistrarTemp(_:)emedia(). Crie uma instância, registre 5 temperaturas e imprima a média. - Desafio: Projete um "Sistema de Colaboração em Equipe": use uma classe
Equipepara gerenciar uma lista compartilhada de membros e uma structMembropara perfis individuais.Equipefornece os métodosadicionarMembro(_:)elistarMembros().Membrotem um métodomutatingpara atualizar funções. Verifique o comportamento de referência compartilhada da classe.