Swift e Objective-C

ARC em Swift: verifique ciclos e a vida de weak e unowned

Crie e rompa um ciclo forte, observe weak virar nil e diferencie uma referência unowned opcional da limpeza automática feita pelo runtime.

2 min de leitura
Imagem de capa de ARC em Swift: verifique ciclos e a vida de weak e unowned

weak e unowned não possuem seus alvos, mas diferem após a liberação. weak vira nil; acessar um alvo liberado por unowned seguro causa erro em execução. Escolha conforme a garantia de vida, não apenas pela sintaxe opcional.

Mecanismo e decisão de projeto

ARC combina operações de propriedade geradas pelo compilador com gerenciamento de referências em execução. Um ciclo forte pode sobreviver aos proprietários externos. unowned admite opcionais, mas cabe ao programador manter um alvo válido ou atribuir nil explicitamente. Fonte primária

Código reproduzível

Primeiro dois Node possuem um ao outro; removemos referências fortes externas, observamos sua sobrevivência com weak e rompemos uma ligação. Depois usamos uma ligação forte do pai ao filho e weak no sentido inverso. Por fim, limpamos um unowned opcional enquanto Owner permanece vivo.

Este exemplo independente foi executado em 2026-10-23 com Apple Swift 6.4 para arm64 macOS, no modo de linguagem Swift 6. Não requer rede nem pacotes adicionais.

Baixar o arquivo Swift completo

Execute o comando na pasta do download. As precondições interrompem a execução se uma verificação falhar. Não use -Ounchecked: essa opção remove as verificações.

swift -swift-version 6 swift-arc-weak-unowned.swift
final class Node { var next: Node? }
func breakCycle(_ node: Node) { node.next = nil }

weak var observedA: Node?
weak var observedB: Node?
do {
    let a = Node()
    let b = Node()
    a.next = b
    b.next = a
    observedA = a
    observedB = b
}
precondition(observedA != nil && observedB != nil)
breakCycle(observedA!)
precondition(observedA == nil && observedB == nil)

final class Owner { var child: Child? }
final class Child { weak var owner: Owner? }
weak var observedOwner: Owner?
weak var observedChild: Child?
do {
    let owner = Owner()
    let child = Child()
    owner.child = child
    child.owner = owner
    observedOwner = owner
    observedChild = child
}
precondition(observedOwner == nil && observedChild == nil)

final class Borrower {
    unowned var owner: Owner?
    init(_ owner: Owner) { self.owner = owner }
}
do {
    let owner = Owner()
    let borrower = Borrower(owner)
    withExtendedLifetime(owner) {
        precondition(borrower.owner === owner)
        borrower.owner = nil
        precondition(borrower.owner == nil)
    }
}
print("cycle=retained broken=released weak=nil unowned-optional=manually-cleared")

Saída registrada e interpretação

cycle=retained broken=released weak=nil unowned-optional=manually-cleared

O ciclo forte sobrevive ao bloco, mas ambas as observações viram nil após romper uma ligação. A relação pai-filho com retorno weak é liberada quando os proprietários fortes desaparecem. O unowned opcional fica nil por atribuição explícita, não por limpeza automática.

Escopo e limitações

O programa não acessa um unowned inválido nem afirma ter medido essa falha. Não generalize a liberação para uma linha exata: otimização e extensão de vida influenciam. A ausência de um log de deinit não prova vazamento; caches ou tarefas assíncronas podem reter objetos legitimamente.

O que verificar em seguida

Desenhe o grafo de propriedade e decida se a ligação inversa deve possuir o alvo. Use weak quando o desaparecimento for válido e considere unowned apenas com garantia de vida durante o acesso. Continue com capturas se uma closure armazenada retém seu proprietário.

Experimento relacionado

Fontes e verificação