Por que o Swift não usa um coletor de lixo tradicional?
\nSe você veio para o mundo Swift vindo do Python ou Java, provavelmente ficou surpreso que esta linguagem não possui um coletor de lixo (Garbage Collector) clássico. Em vez disso, a Apple implementou o mecanismo Automatic Reference Counting (ARC) — contagem automática de referências. E isso não é um capricho: em dispositivos móveis, cada ciclo de processador e cada byte de memória contam. O GC tradicional "congela" o aplicativo durante a coleta, o que para o iOS, com sua exigência de animações suaves (60 FPS), seria um desastre.
\nNeste artigo, vamos analisar como o gerenciamento de memória realmente funciona em Swift, onde estão as armadilhas com referências cíclicas e como escrever código que não "vaza" para lugar nenhum.
\n\nARC: como funciona por baixo dos panos?
\nARC não é um coletor de lixo no sentido tradicional. Ele funciona na fase de compilação: o compilador insere instruções retain e release nos lugares certos. Mas há um detalhe: o ARC não sabe lidar com referências cíclicas (retain cycles). Se dois objetos se seguram com referências fortes, seus contadores de referência nunca cairão para zero — a memória vaza.
Exemplo: classe simples e contagem de referências
\nclass Person {\n let name: String\n init(name: String) {\n self.name = name\n print("\\(name) apareceu na memória")\n }\n deinit {\n print("\\(name) desapareceu da memória")\n }\n}\n\nvar person1: Person? = Person(name: "Alice") // retain count = 1\nvar person2 = person1 // retain count = 2\nperson1 = nil // retain count = 1\nperson2 = nil // retain count = 0 → deinit\n\nNo console, você verá: "Alice apareceu na memória" e, após algum tempo, "Alice desapareceu da memória". Tudo correto.
\n\nReferências cíclicas: o principal inimigo da memória
\nImagine: você tem um User e um Account. O usuário tem uma conta, e a conta conhece seu dono. Se ambas as propriedades forem referências fortes, forma-se um círculo vicioso:
class User {\n let name: String\n var account: Account?\n init(name: String) { self.name = name }\n deinit { print("User \\(name) removido") }\n}\n\nclass Account {\n let number: String\n var owner: User?\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) removido") }\n}\n\nvar user: User? = User(name: "Boris")\nvar account: Account? = Account(number: "12345")\nuser?.account = account\naccount?.owner = user\n\nuser = nil // User não removido! account?.owner ainda mantém a referência\naccount = nil // Account não removido! user?.account mantém a referência\n// Vazamento de memória!\n\nNenhuma mensagem deinit aparecerá no console. Os objetos ficam presos na memória para sempre. Isso é um retain cycle — um problema clássico de gerenciamento de memória em Swift.
Como tratar? Weak e unowned
\nA solução é tornar uma das referências fraca (weak) ou não proprietária (unowned). A diferença:
- \n
- weak — sempre opcional, torna-se automaticamente
nilquando o objeto é removido. Seguro. \n - unowned — não opcional, assume que o objeto vive mais tempo. Se acessar um objeto removido — crash. \n
class Account {\n let number: String\n weak var owner: User? // ← referência fraca\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) removido") }\n}\n// Agora user = nil liberará User, e account?.owner se tornará nil\n\n\nColetor de lixo vs ARC: qual é mais rápido?
\nVamos ser honestos: ARC não é um "coletor de lixo" no sentido clássico. Um GC real (como em Java, Go ou Python) funciona em tempo de execução: ele periodicamente varre o heap, encontra objetos inacessíveis e os libera. Isso requer pausas (stop-the-world).
\nARC, por outro lado, é estático