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


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

SWIFT
// 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.

100%
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

SWIFT
// ============================================
// 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 leitura
Alice 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.

100%
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

SWIFT
// ============================================
// 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

SWIFT
// ============================================
// 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

SWIFT
// ============================================
// 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

SWIFT
// ============================================
// 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

SWIFT
// ============================================
// 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


📝 Exercícios

  1. Básico: Crie duas classes Apartamento e Inquilino. 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.
  2. Intermediário: Escreva uma classe GerenciadorTimer que mantém internamente um Timer e 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.
  3. 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.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%