Swift y Objective-C

ARC en Swift: verifica ciclos y vidas de weak y unowned

Crea y rompe un ciclo fuerte, observa weak convertirse en nil y distingue una referencia unowned opcional del borrado automático.

2 min de lectura
Imagen de portada de ARC en Swift: verifica ciclos y vidas de weak y unowned

weak y unowned no poseen su destino, pero difieren tras liberarlo. weak pasa a nil; acceder a un destino liberado mediante unowned seguro causa un error en ejecución. Decide según garantías de vida, no solo por la sintaxis opcional.

Mecanismo y decisión de diseño

ARC combina operaciones de propiedad generadas por el compilador con gestión de referencias en ejecución. Un ciclo fuerte puede sobrevivir a sus propietarios externos. unowned admite opcionales, pero debes mantener una referencia válida o asignarle nil explícitamente. Fuente primaria

Código reproducible

Primero dos Node se poseen mutuamente; eliminamos referencias fuertes externas, observamos su supervivencia con weak y rompemos una conexión. Después usamos una relación fuerte de padre a hijo y débil en sentido inverso. Finalmente limpiamos un unowned opcional mientras Owner sigue vivo.

Este ejemplo independiente se ejecutó el 2026-10-23 con Apple Swift 6.4 para arm64 macOS, en modo de lenguaje Swift 6. No necesita red ni paquetes adicionales.

Descargar el archivo Swift completo

Ejecuta el comando en la carpeta de descarga. Las precondiciones detienen la ejecución si falla una comprobación. No uses -Ounchecked: elimina estas comprobaciones.

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")

Salida registrada e interpretación

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

El ciclo fuerte sobrevive al bloque, pero ambas observaciones pasan a nil al romper una conexión. La relación padre-hijo con retorno weak se libera al desaparecer los propietarios fuertes. El unowned opcional es nil por asignación explícita, no por borrado automático.

Alcance y limitaciones

El programa no accede a un unowned inválido ni afirma haber medido ese fallo. No generalices una liberación a una línea exacta: influyen optimización y extensión de vida. La ausencia de un registro de deinit no prueba una fuga; una caché o tarea asíncrona puede retener legítimamente.

Qué comprobar después

Dibuja el grafo de propiedad y decide si la relación inversa debe poseer el destino. Usa weak si su desaparición es válida y considera unowned solo con garantía de vida durante el acceso. Continúa con capturas si una clausura almacenada retiene a su propietario.

Experimento relacionado

Fuentes y verificación