¿Cómo hacer una referencia débil a un protocolo en Swift puro (sin @objc)? Guía completa con ejemplos
\n\nImagine: está escribiendo una aplicación en Swift, usa el patrón Delegate, y de repente — ¡bam! — sus objetos no se destruyen, la memoria crece y la aplicación comienza a ralentizarse. ¿Le suena familiar? Lo más probable es que se haya encontrado con un retain cycle (ciclo de referencias fuertes). Un problema clásico cuando el delegado mantiene una referencia fuerte al propietario, y el propietario mantiene una referencia fuerte al delegado. La solución que viene a la mente primero es hacer la referencia al protocolo débil (weak). Pero aquí Swift nos da una sorpresa: no puede simplemente declarar weak var delegate: MyProtocol si el protocolo no está marcado con @objc. El compilador dará un error. ¿Por qué? Y lo más importante: ¿cómo solucionarlo?
En este artículo, analizaremos cómo hacer una weak protocol reference en Swift puro sin @objc. Aprenderá por qué esto es un problema, cuáles son las soluciones y cómo escribir código limpio y seguro sin fugas de memoria. ¡Vamos allá!
\n\n¿Por qué weak protocol reference no funciona «de fábrica»?
\n\nRecordemos cómo funciona weak en Swift. Una referencia débil (weak) solo puede existir para reference types (clases). ¿Por qué? Porque solo los objetos de clases tienen gestión de memoria a través de ARC (Automatic Reference Counting). Las estructuras (struct) y las enumeraciones (enum) son value types, se copian, y para ellas el concepto de «referencia débil» no tiene sentido.
Cuando escribe:
\nweak var delegate: MyProtocol?\nEl compilador no sabe si MyProtocol será implementado por una clase, una estructura o una enumeración. ¿Y si es una estructura? Entonces weak no se puede aplicar. De ahí el error: «'weak' may only be applied to class and class-bound protocol types».
Antes (en Objective-C), todos los protocolos por defecto solo podían ser implementados por clases, por lo que weak funcionaba. En Swift puro, los protocolos son universales — y eso es genial, pero añade complejidad.
Solución n.º 1: class-bound protocol (protocolo restringido a clases)
\n\nLa forma más simple y común es indicar explícitamente que el protocolo solo puede ser implementado por clases. Para ello se usa la palabra clave AnyObject (o la obsoleta class) en la declaración del protocolo.
Ejemplo:
\nprotocol MyDelegate: AnyObject {\n func didSomething()\n}\n\nclass ViewController {\n weak var delegate: MyDelegate?\n\n func doSomething() {\n delegate?.didSomething()\n }\n}\n\nclass SomeClass: MyDelegate {\n func didSomething() {\n print("¡El delegado funcionó!")\n }\n}\n\n// Uso\nlet vc = ViewController()\nlet some = SomeClass()\nvc.delegate = some\nvc.doSomething() // Imprime: "¡El delegado funcionó!"\n\nTodo funciona. Ahora MyDelegate solo puede ser implementado por una clase (class), y weak es legal. Este es el enfoque recomendado por Apple para delegados.
¿Por qué es bueno?
\n- \n
- Simplicidad y legibilidad. \n
- Soporte completo de ARC. \n
- No es necesario usar
@objc. \n
¿Desventajas?
\n- \n
- Pierde la posibilidad de implementar el protocolo con una estructura. Pero para delegados, esto generalmente no es necesario. \n
Solución n.º 2: Uso de un generic wrapper (para casos exóticos)
\n\n¿Qué pasa si debe permitir que el protocolo sea implementado tanto por clases como por estructuras, pero aún así quiere una referencia débil? Esto es raro, pero posible. Por ejemplo, si está escribiendo una biblioteca donde el usuario puede pasar tanto una clase como una estructura. Entonces, un envoltorio (wrapper) viene al rescate.
\n\nLa idea: crear una estructura que almacene una referencia débil al objeto que implementa el protocolo. Para clases — we