Sobes.tech
Middle

¿Siempre es necesario usar weak o unowned en la lista de captura?

sobes.tech AI

Respuesta de la IA

No, no siempre.

Utilizamos weak o unowned en la lista de captura para prevenir ciclos de retención.

Un ciclo de retención ocurre cuando dos o más objetos se refieren entre sí de manera fuerte, impidiendo que la memoria sea liberada.

Se debe usar weak o unowned cuando un cierre hace referencia fuerte a self (u otra variable de instancia de clase/actor), y a su vez self (u otro objeto) tiene una referencia fuerte a ese cierre.

Ejemplos de situaciones donde esto es necesario:

  • Operaciones asíncronas (solicitudes de red, procesamiento de datos)
  • Patrón Observer
  • Bloques de animación
  • Colas de despacho

Si el cierre no captura self (u otra variable de clase/actor que tenga una referencia fuerte al cierre), o captura tipos estructurales/enumeraciones, no es necesario usar weak o unowned.

Diferencias entre weak y unowned:

Tipo Descripción Cuándo usar
weak Referencia débil (opcional). Puede ser nil. Cuando el ciclo de vida del cierre es más corto o igual al ciclo de vida del objeto capturado, y el objeto puede volverse nil antes de que termine el cierre.
unowned Referencia no opcional. No puede ser nil. Cuando el ciclo de vida del cierre es más corto o igual al ciclo de vida del objeto capturado, y garantiza que el objeto existirá hasta que termine el cierre. Si el objeto se libera antes de que se ejecute el cierre, ocurrirá un error (referencia unowned a una instancia liberada).

Ejemplo de uso de weak en la lista de captura:

class MyClass {
    var name: String = "Test"

    func performAsyncOperation() {
        // El cierre captura `self`.
        // MyClass puede ser liberado antes de que termine la operación asíncrona.
        DispatchQueue.global().async { [weak self] in
            guard let self = self else {
                // `self` fue liberado, salir
                return
            }
            print("Operación completada para \(self.name)")
        }
    }

    deinit {
        print("MyClass liberado")
    }
}

Ejemplo de uso de unowned en la lista de captura:

class ViewController {
    var dismissButton: UIButton!

    func setupButton() {
        dismissButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)

        // El cierre captura `self`.
        // Suponemos que `self` (ViewController) vive más o igual que la acción del botón.
        // En este caso, el target-acción no es un cierre, pero ilustra la idea de `unowned` en ciclos de referencia fuertes.
        // Si esto *fuera* un cierre, por ejemplo, en un manejador de evento UIControl.Event,
        // y ViewController existe durante el clic:
        // dismissButton.addAction(UIAction { [unowned self] _ in
        //     self.dismiss(animated: true, completion: nil)
        // }, for: .touchUpInside)
    }

    @objc func dismiss() {
        // Cerrar el controlador de vista
    }
}

Si el cierre no crea un ciclo de retención, no es obligatorio usar weak o unowned, e incluso puede causar verificaciones innecesarias (en caso de weak) o errores potenciales (en caso de unowned).

Por ejemplo, un cierre que no captura self u otro objeto, que tiene una referencia fuerte en el cierre:

func fetchData(completion: @escaping (Data) -> Void) {
    URLSession.shared.dataTask(with: URL(string: "...")!) { data, response, error in
        guard let data = data else { return }
        // Aquí no hay captura de `self` u otros objetos que puedan causar un ciclo de retención.
        completion(data)
    }.resume()
}

En este caso, no se requiere [weak self] ni [unowned self] en la lista de captura.