Swift: Gerenciamento de Memória e ARC do Swift
O ARC é o gerenciador de memória do Swift -- ele rastreia e gerencia automaticamente as contagens de referência dos objetos. Entenda-o e seu app não consumirá silenciosamente toda a memória.
1. O Que Você Vai Aprender
- Como funciona o ARC (Contagem Automática de Referência)
- As diferenças principais entre referências strong, weak e unowned
- Causas e cenários comuns de ciclos de retenção
- Corrigindo ciclos de retenção com listas de captura de closure
- Detectando vazamentos de memória com Instruments
2. Uma História Real de um Desenvolvedor iOS
(1) Problema: O App Fica Mais Lento e Depois Trava
Bob descobriu um problema bizarro ao testar seu app de chat: após navegar para dentro e para fora da página de chat 5 vezes, o app consumia 500MB de memória e travava completamente na 6ª vez. Usando o depurador de memória do Xcode, ele descobriu que cada entrada na página de chat aumentava a memória em 80MB, mas apenas 10MB eram liberados na saída.
A causa raiz: os objetos Mensagem e Usuario da página de chat mantinham referências fortes entre si, formando um ciclo de retenção. O ARC considerava que "ambos os lados precisam um do outro", então nenhum era liberado.
(2) A Solução com Referência Fraca
// Before fix: retain cycle
class Message {
let text: String
var sender: User? // Strong reference
}
class User {
let name: String
var lastMessage: Message? // Strong reference
}
// After fix: use weak reference on one side to break the cycle
class User {
let name: String
weak var lastMessage: Message? // Weak reference
}
(3) Benefícios: Uso de Memória Estável e Controlável
| Dimensão | Antes da Correção | Após a Correção |
|---|---|---|
| 10 navegações de página | 480MB → travamento | 60MB estável |
| Desalocação de objetos | 60% não podem liberar | 100% desalocados normalmente |
| Experiência do usuário | Fica mais lento com o tempo | Suave o tempo todo |
| Custo de manutenção | Não sabe onde está o vazamento | Relações de referência claras |
3. Como o ARC Funciona
O ARC (Contagem Automática de Referência) rastreia automaticamente quantas variáveis referenciam cada instância de classe. Quando a contagem de referência chega a zero, a instância é automaticamente liberada.
graph LR
A["Instância Pessoa"] --> B["Contagem ref = 0: desalocada"]
A --> C["Contagem ref = 1: viva"]
A --> D["Contagem ref = 2: viva"]
B --> E["var alice: Pessoa? = Pessoa()"]
C --> F["alice = nil -> contagem 0 -> deinit"]
D --> G["var ref2 = alice -> contagem 2"]
G --> H["ref2 = nil -> contagem 1"]
H --> I["alice = nil -> contagem 0 -> deinit"]
(1) Regras de Contagem de Referência
| Operação | Mudança na Contagem | Descrição |
|---|---|---|
| Criar instância | 0 -> 1 | let obj = MinhaClasse() |
| Atribuir a uma referência forte | +1 | let ref2 = obj |
| Definir referência forte como nil | -1 | ref2 = nil |
| Sair do escopo | -1 | Variável local destruída automaticamente |
(2) Observando a Desalocação com deinit
▶ Exemplo: Observando Mudanças na Contagem de Referência
// ============================================
// Observing ARC reference count and deinit timing
// ============================================
class Person {
let name: String
init(name: String) { self.name = name; print("\(name) initialized") }
deinit { print("\(name) deallocated") }
}
var alice: Person? = Person(name: "Alice") // count = 1
var ref2 = alice // count = 2
print("Before ref2 = nil")
ref2 = nil // count = 1
print("After ref2 = nil")
print("Before alice = nil")
alice = nil // count = 0 -> deinit
print("After alice = nil")
Saída:
TEXT 📖 Somente leituraAlice initialized Before ref2 = nil After ref2 = nil Before alice = nil Alice deallocated After alice = nil
4. O Problema do Ciclo de Retenção
Quando dois objetos mantêm referências fortes entre si, o ARC não pode liberá-los -- ambos têm contagens de referência >= 1 o tempo todo.
graph TB
A["Mensagem\ncontagem ref: 1"] -->|"ref forte remetente"| B["Usuario\ncontagem ref: 1"]
B -->|"ref forte ultimaMensagem"| A
C["Ref externa mensagem = nil"] -.->|"mas ainda não liberado"| A
D["Ref externa usuario = nil"] -.->|"mas ainda não liberado"| B
style A fill:#ffcccc
style B fill:#ffcccc
(1) Ciclos de Retenção Entre Instâncias de Classe
| Combinação de Referência | Pode Liberar? | Descrição |
|---|---|---|
| A strong -> B, B strong -> A | Vazamento | Ciclo de retenção clássico |
| A strong -> B, B weak -> A | Normal | Um lado weak quebra o ciclo |
| A strong -> B, B unowned -> A | Normal | Um lado unowned (tempo de vida de B <= A) |
▶ Exemplo: Ciclo de Retenção em Mensagem de Chat
// ============================================
// Retain cycle demo: Message and User hold each other
// ============================================
class Message {
let text: String
var sender: User?
init(text: String) { self.text = text; print("Message initialized: \(text)") }
deinit { print("Message deallocated: \(text)") }
}
class User {
let name: String
var lastMessage: Message?
init(name: String) { self.name = name; print("User initialized: \(name)") }
deinit { print("User deallocated: \(name)") }
}
print("--- Create ---")
var user: User? = User(name: "Bob")
var msg: Message? = Message(text: "Hello")
user?.lastMessage = msg
msg?.sender = user
print("--- Set nil ---")
msg = nil
user = nil
print("--- Observe output ---")
print("Notice: deinit was NOT triggered -- objects leaked!")
Saída:
TEXT 📖 Somente leitura--- Create --- User initialized: Bob Message initialized: Hello --- Set nil --- --- Observe output --- Notice: deinit was NOT triggered -- objects leaked!
(2) Ciclos de Retenção em Closures
Closures que capturam variáveis externas também criam referências fortes. Se uma closure é mantida por um objeto e a closure captura esse mesmo objeto, um ciclo é formado.
▶ Exemplo: Ciclo de Retenção por Closure
// ============================================
// Retain cycle caused by a closure
// ============================================
class NetworkManager {
var onComplete: (() -> Void)?
let url: String
init(url: String) { self.url = url; print("Manager initialized") }
deinit { print("Manager deallocated") }
func start() {
onComplete = {
print("Request completed: \(self.url)")
}
}
}
print("--- Create ---")
var manager: NetworkManager? = NetworkManager(url: "https://api.example.com")
manager?.start()
print("--- Set nil ---")
manager = nil
print("--- Observe ---")
print("deinit NOT triggered -- closure captures self, causing a leak")
Saída:
TEXT 📖 Somente leitura--- Create --- Manager initialized --- Set nil --- --- Observe --- deinit NOT triggered -- closure captures self, causing a leak
5. Referências Weak e Unowned
Tanto weak quanto unowned não aumentam a contagem de referência, mas suas semânticas diferem ligeiramente.
(1) A Palavra-chave weak
Uma referência fraca deve poder se tornar nil em tempo de execução -- então deve ser declarada como var e um tipo optional ?.
(2) A Palavra-chave unowned
Uma referência unowned assume que o objeto referenciado não será liberado antes de si mesmo -- não é necessário tipo optional, mas usar um objeto liberado causará falha.
| Característica | weak | unowned |
|---|---|---|
| Aumenta a contagem de referência? | Não | Não |
| Deve ser optional? | Deve ser Optional | Não-optional |
| Após o objeto ser liberado | Automaticamente definido como nil | Aponta para um ponteiro inválido |
| Seguro de usar? | Seguro (verificação nil) | Seguro apenas se o objeto existir |
| Caso de uso | Incerteza se o outro está vivo | Garantia de que o outro está vivo |
▶ Exemplo: Corrigindo Ciclo de Retenção com weak
// ============================================
// Using weak to break the retain cycle between classes
// ============================================
class Message {
let text: String
weak var sender: User? // weak breaks the cycle
init(text: String) { self.text = text; print("Message initialized: \(text)") }
deinit { print("Message deallocated: \(text)") }
}
class User {
let name: String
var lastMessage: Message?
init(name: String) { self.name = name; print("User initialized: \(name)") }
deinit { print("User deallocated: \(name)") }
}
print("--- After fix ---")
var user: User? = User(name: "Bob")
var msg: Message? = Message(text: "Hello")
user?.lastMessage = msg
msg?.sender = user
print("--- Set nil ---")
msg = nil // Message freed
user = nil // User freed
print("--- Deallocated normally ---")
Saída:
TEXT 📖 Somente leitura--- After fix --- User initialized: Bob Message initialized: Hello --- Set nil --- Message deallocated: Hello User deallocated: Bob --- Deallocated normally ---
(3) Corrigindo com Listas de Captura de Closure
Use [weak self] ou [unowned self] para declarar como a closure captura variáveis no momento da definição.
▶ Exemplo: Corrigindo Ciclo de Retenção de Closure com Lista de Captura
// ============================================
// Capture list fixes the closure retain cycle
// ============================================
class NetworkManager {
var onComplete: (() -> Void)?
let url: String
init(url: String) { self.url = url; print("Manager initialized") }
deinit { print("Manager deallocated") }
func start() {
onComplete = { [weak self] in
guard let self = self else { return }
print("Request completed: \(self.url)")
}
}
}
print("--- After fix ---")
var manager: NetworkManager? = NetworkManager(url: "https://api.example.com")
manager?.start()
manager = nil
print("--- Manager deallocated ---")
Saída:
TEXT 📖 Somente leitura--- After fix --- Manager initialized --- Manager deallocated --- Manager deallocated
6. Exemplo Completo: Gerenciamento de Mensagens de App de Chat
// ============================================
// Full example: Memory-safe chat message management
// Uses weak/unowned + capture lists
// ============================================
import Foundation
// 1. User model
class UserProfile {
let id: Int
let name: String
weak var latestMessage: Message? // weak breaks the cycle
var messages: [Message] = []
init(id: Int, name: String) {
self.id = id
self.name = name
print("UserProfile \(name) initialized")
}
deinit { print("UserProfile \(name) deallocated") }
// 2. Use capture list in closure
func createAutoReply() -> (() -> String) {
return { [weak self] in
guard let self = self else { return "User is offline" }
return "\(self.name): Auto-reply - I'll get back to you later"
}
}
}
// 3. Message model
class Message {
let id: Int
let text: String
var sender: UserProfile? // Sender
init(id: Int, text: String, sender: UserProfile?) {
self.id = id
self.text = text
self.sender = sender
print("Message #\(id) initialized")
}
deinit { print("Message #\(id) deallocated") }
}
// 4. Usage example
print("=== Send message ===")
var alice: UserProfile? = UserProfile(id: 1, name: "Alice")
var msg1: Message? = Message(id: 101, text: "Hello!", sender: alice)
alice?.latestMessage = msg1
msg1?.sender = alice
let reply = alice?.createAutoReply()
print(reply?() ?? "")
print("\n=== Exit chat ===")
msg1 = nil
alice = nil
print("=== All objects deallocated ===")
Saída:
TEXT 📖 Somente leitura=== Enviar mensagem === PerfilUsuario Alice inicializado Mensagem #101 inicializada Alice: Resposta automática - Retornarei mais tarde === Sair do chat === Mensagem #101 desalocada PerfilUsuario Alice desalocado === Todos os objetos desalocados ===
❓ Perguntas Frequentes
P: Como escolher entre weak e unowned? R: Se o objeto referenciado pode ser liberado primeiro (ex.: delegate de um view controller), use weak. Se o objeto referenciado tem garantia de sobreviver à referência (ex.: pai referencia filho em uma relação pai-filho), use unowned. Na dúvida, use weak. P: Quando devo obrigatoriamente usar uma lista de captura de closure? R: Quando uma closure é fortemente mantida por um objeto (armazenada em uma propriedade) e a closure acessa self internamente, você deve usar
[weak self]ou[unowned self]. Closures temporárias (como conclusões de animação) não precisam. P: Structs também têm ciclos de retenção? R: Não. Structs são tipos por valor e não têm contagem de referência. Apenas classes (tipos por referência) podem criar ciclos de retenção e vazamentos de memória. P: Como detectar ciclos de retenção em um app? R: Use o Depurador de Grafo de Memória do Xcode -- execute o app, clique no botão de grafo de memória e objetos vazados são marcados em roxo. Você também pode usar o template Leaks do Instruments. P: Posso declarar todas as propriedades como weak? R: Não. weak significa "não possuir", e se todas as propriedades forem weak, o objeto é liberado imediatamente. Deve haver uma cadeia de referências fortes dos objetos raiz (AppDelegate, Window) para todos os objetos ativos.
📖 Resumo
- O ARC gerencia automaticamente a memória das instâncias de classe; as instâncias são liberadas quando a contagem de referência chega a zero
- Um ciclo de retenção ocorre quando dois objetos mantêm referências fortes entre si, impedindo que qualquer um seja liberado
- Referências fracas não aumentam a contagem de referência e são automaticamente definidas como nil quando o objeto é liberado
- Referências unowned não aumentam a contagem de referência e não se tornam nil -- acessar um objeto liberado causa uma falha
- Listas de captura de closure
[weak self]quebram ciclos de retenção causados por closures - Use o Depurador de Grafo de Memória do Xcode para detectar visualmente ciclos de retenção
📝 Exercícios
- Básico: Crie duas classes
ApartamentoeInquilino. Inquilino mantém uma referência ao apartamento alugado; Apartamento mantém uma lista de inquilinos. Use weak para evitar ciclos de retenção e verifique se deinit é acionado corretamente. - Intermediário: Escreva uma classe
GerenciadorTimerque mantém internamente umTimere usa uma closure como callback. Acesse as propriedades e métodos de self na closure, usando uma lista de captura para evitar um ciclo de retenção. - Desafio: Simule um cenário comum de vazamento -- iniciar uma requisição de rede em um view controller e atualizar a UI no callback da closure. Construa a versão com vazamento, depois corrija-a. Use print no deinit para verificar se a correção funciona.