Retain Cycle — essência, causas e eliminação no desenvolvimento de aplicativos

Autor: IT Sectr Publicado: 2026-03-29 Tempo de leitura: 8 min

Retain Cycle é uma situação em ARC onde dois ou mais objetos se referenciam mutuamente por meio de referências fortes, formando um ciclo fechado. De acordo com o Apple Memory Management Guide, 2026, um retain cycle bloqueia a liberação de todos os objetos no ciclo porque cada um tem um retain count ≥ 1. Diferentemente de um vazamento de memória em GC, um retain cycle garante que os objetos permaneçam vivos enquanto pelo menos um participante externo do ciclo estiver vivo — e mesmo após perder todas as referências externas, se o ciclo estiver isolado.

Principais pontos

  • Retain Cycle — uma cadeia fechada de referências fortes que impede a ARC de liberar os objetos
  • Causa — dois (ou mais) objetos mantêm referências fortes entre si, impossibilitando a zeragem do retain count
  • Consequência — vazamento de memória: os objetos permanecem na memória para sempre, o consumo de RAM aumenta
  • Solução — substituir uma das referências fortes no ciclo por weak ou unowned
  • Diagnóstico — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

O que é Retain Cycle?

Retain Cycle é uma situação em que dois ou mais objetos possuem um ao outro por meio de referências fortes, criando um grafo de dependências fechado. ARC não pode liberar nenhum desses objetos porque o retain count de cada um é sempre ≥ 1: o objeto A retém B, B retém A, e seus contadores nunca chegam a zero.

O problema ocorre exclusivamente em sistemas de contagem de referências (ARC, MRR). No Garbage Collection, o coletor determina a inacessibilidade por meio do grafo de referências a partir do conjunto raiz — ciclos não são um obstáculo. No ARC, no entanto, um ciclo equivale a um vazamento, porque a liberação determinística por contagem não pode resolver dependências circulares.

De acordo com a WWDC 2012 Session 406, retain cycle é a causa mais comum de vazamentos de memória em aplicativos Objective-C e Swift. Cenários típicos: relacionamentos pai-filho com delegados, closures que capturam self e arquiteturas em camadas com relacionamentos bidirecionais.

Exemplos de retain cycle no desenvolvimento iOS

Vamos examinar cenários clássicos de retain cycle que todo desenvolvedor iOS encontra. Compreender esses padrões é a base para escrever código seguro com ARC.

Parent-Child com delegado

Cenário clássico: um objeto pai (por exemplo, UIViewController) cria um objeto filho e se torna seu delegado. Se ambos usarem referências fortes, ocorre um retain cycle. A solução — o delegado deve ser weak.

swift
// ERRO: retain cycle por meio de delegate forte
protocol ChildDelegate: AnyObject { }

class ParentVC: UIViewController, ChildDelegate {
    var child: ChildVC?

    func showChild() {
        child = ChildVC()
        child?.delegate = self        // Parent → Child (strong)
    }                                 // Child → Parent (strong via delegate)
}                                     // ⚠️ Retain cycle!

class ChildVC: UIViewController {
    var delegate: ChildDelegate?    // ❌ strong por padrão
}

// CORREÇÃO: delegate weak
class ChildVC: UIViewController {
    weak var delegate: ChildDelegate? // ✅ weak — não retém
}

No exemplo, ParentVC mantém uma referência forte a ChildVC por meio da propriedade child. ChildVC mantém uma referência forte a ParentVC por meio de delegate. O ciclo está fechado. Correção: weak var delegate — a referência não aumenta o retain count, e ParentVC pode ser liberado.

NSTimer e retain cycle

NSTimer é uma fonte clássica de retain cycles. O timer retém seu target (geralmente self), e o target retém o timer por meio de uma propriedade. Mesmo que o timer seja de uso único, ele não será liberado até que invalidate seja chamado. Solução: sempre chamar timer.invalidate() em deinit ou viewDidDisappear.

Arquiteturas em camadas

Em arquiteturas com propriedade em cascata (coordenadores, roteadores), ciclos de múltiplas etapas frequentemente ocorrem: Coordinator → ViewController → ViewModel → Coordinator (por meio de um callback). Cada referência forte na cadeia deve ser conscientemente escolhida — uma referência weak em qualquer elo quebra o ciclo.

Retain Cycle em closures Swift

Closures em Swift capturam variáveis externas por referência forte. Se uma closure for armazenada como propriedade de um objeto (por exemplo, um completion handler) e capturar self, ela cria um retain cycle: self → closure → self.

Esta é a fonte mais comum de retain cycles no desenvolvimento moderno com Swift. Ocorre implicitamente — um desenvolvedor pode não notar a captura de self em uma closure, especialmente ao usar sintaxe abreviada sem self explícito.

swift
class DownloadService {
    var onComplete: ((Data) -> Void)?
    var result: Data?

    func startDownload() {
        // ❌ Retain cycle: self → onComplete → self
        onComplete = { data in
            self.result = data
            self.notifyUI()
        }

        // ✅ Correção: capture list com weak self
        onComplete = { [weak self] data in
            guard let self else { return }
            self.result = data
            self.notifyUI()
        }
    }

    func notifyUI() { }
}

Uma lista de captura [weak self] cria uma referência fraca a self dentro da closure. Se DownloadService for liberado antes da execução da closure, self se torna nil, e o código sai com segurança por meio de guard. Este é um padrão padrão para closures assíncronas em Swift — deve ser usado sempre que uma closure for armazenada como propriedade.

Unowned self em closures

unowned self é uma alternativa a weak self quando self tem garantia de viver mais que a closure. Exemplo: closures síncronas que executam imediatamente (sorted, filter). Nesses casos, self está definitivamente vivo, e unowned é seguro. No entanto, unowned trava ao acessar um objeto liberado — portanto, weak é considerado a opção segura padrão.

Como detectar retain cycle: ferramentas de diagnóstico

Detectar retain cycles em um estágio inicial é criticamente importante para o desempenho do aplicativo. Vamos revisar as principais ferramentas e técnicas para identificar referências cíclicas no desenvolvimento iOS.

Xcode Memory Debugger

Xcode Memory Debugger (Debug Memory Graph) é uma ferramenta visual que mostra o grafo de objetos na memória com suas referências. Um retain cycle aparece como uma cadeia fechada de setas fortes. Para iniciar: clique no botão Debug Memory Graph no painel Debug area enquanto o aplicativo está em execução. Cada objeto é mostrado com seu tipo, endereço e lista de referências.

Instruments Leaks

Instruments Leaks é um profiler para detecção automática de vazamentos. Ele grava alocações e analisa o grafo de referências em tempo real. Detecta não apenas retain cycles, mas também referências esquecidas, ViewControllers não liberados e outros vazamentos. Leaks aponta o objeto exato e a cadeia de retenção.

Registro de deinit

O método mais simples é adicionar um print no deinit de cada classe chave. Se deinit não for chamado quando se espera que o objeto seja destruído, há um retain cycle. Este método não requer ferramentas e é eficaz para diagnóstico inicial.

FerramentaTipoQuando usar
Memory DebuggerGrafo visualVerificação manual após navegação
Instruments LeaksAnálise automatizadaTestes de regressão, CI
deinit printRegistro manualDesenvolvimento, code review
Malloc ScribbleSinalizador de runtimeDepuração de use-after-free

Abordagem recomendada: use registro de deinit durante o desenvolvimento, Memory Debugger durante os testes manuais e Instruments Leaks no pipeline de CI/CD para detecção automatizada de regressões de vazamento.

Prevenção de retain cycle e melhores práticas

Prevenir retain cycles é mais fácil do que corrigi-los em produção. Aqui estão algumas regras que minimizam o risco de referências cíclicas.

Regra do delegado weak

Todos os delegados e dataSources devem ser weak. Esta regra está incorporada no UIKit: todos os protocolos de delegados no SDK da Apple são declarados com propriedades weak (UITableView.delegate, UICollectionView.dataSource). Para seus próprios protocolos, use weak var delegate: MyDelegate? e herde o protocolo de AnyObject.

Lista de captura em closures

Qualquer closure que seja armazenada como propriedade (completion handler, callback) e capture self deve usar [weak self] na lista de captura. A exceção são closures que executam imediatamente e não são armazenadas (sorted, map, filter). Para elas, unowned self é seguro.

Revisão de arquitetura

Em arquiteturas complexas (VIPER, Coordinators, Redux), rastreie a direção das referências fortes. O proprietário mantém uma referência forte ao subordinado, mas o subordinado deve referenciar o proprietário apenas por meio de weak ou unowned. O fluxo de dados unidirecional simplifica o gerenciamento de referências.

swift
// Exemplo: verificação com registro de deinit
class BaseViewController: UIViewController {
    deinit {
        print("✅ \(type(of: self)) deallocated")
    }
}

// Uso: todos os ViewController herdam BaseViewController
class ProfileVC: BaseViewController {
    var viewModel: ProfileViewModel?
    var onLogout: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()
        onLogout = { [weak self] in
            self?.dismiss(animated: true)
        }
    }
}
// Ao fechar ProfileVC esperamos "✅ ProfileVC deallocated" no console

Uma classe base com registro de deinit fornece feedback instantâneo. Se a mensagem não aparecer quando a tela deve ser fechada, há um retain cycle nesta classe. Adicione esta prática ao template do projeto para todos os ViewControllers.

Perguntas frequentes

Como o retain cycle difere de um vazamento de memória em GC?

Retain cycle é um problema específico do ARC onde um ciclo fechado de referências fortes bloqueia a liberação. No GC, o coletor analisa a acessibilidade a partir do conjunto raiz, não contadores de referência — portanto, ciclos não são vazamentos. No ARC, no entanto, qualquer ciclo isolado é um vazamento garantido.

Como uma referência weak quebra um retain cycle?

Uma referência weak não aumenta o retain count de um objeto. Se você substituir uma das referências fortes em um ciclo por weak, o retain count de cada objeto pode chegar a zero. Após o objeto ser liberado, a referência weak é automaticamente definida como nil, impedindo o acesso à memória liberada.

Um retain cycle pode consistir de três ou mais objetos?

Sim, um retain cycle pode incluir qualquer número de objetos: A → B → C → A. Para quebrá-lo, você só precisa quebrar um elo no ciclo — substitua qualquer referência forte por weak ou unowned. As ferramentas mostram o grafo completo, não apenas pares de objetos.

Por que o GCD DispatchWorkItem não cria um retain cycle?

GCD (Grand Central Dispatch) não armazena a closure após a execução. O DispatchWorkItem executa e é liberado, mesmo que a closure capture self. Um retain cycle ocorre apenas quando uma closure é armazenada como propriedade (completion handler em uma classe), não quando é passada para uma fila.

Que tipos de retain cycle o Instruments não detecta?

Instruments Leaks nem sempre encontra retain cycles temporários (que duram segundos) ou referências cíclicas em objetos C/C++ por meio de bridging. Para uma verificação completa, use Memory Debugger manualmente junto com o registro de deinit de todos os objetos chave na cena.

Resumo

  • Retain Cycle — uma cadeia fechada de referências fortes que bloqueia a liberação de objetos no ARC
  • Causas — delegados com referência forte, closures capturando self, relacionamentos pai-filho bidirecionais
  • Solução — substituir uma referência forte por weak ou unowned quebra o ciclo
  • Closures — completion handlers armazenados devem sempre usar [weak self]
  • Delegados — sempre weak; o protocolo de delegado deve herdar de AnyObject
  • Detecção — Xcode Memory Debugger, Instruments Leaks, registro de deinit
  • Prevenção — fluxo de dados unidirecional, delegados weak, listas de captura, classe base com deinit

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também