¿Cómo puedo hacer una referencia de protocolo débil en Swift 'puro' (sin @objc)

561

weakLas referencias no parecen funcionar en Swift a menos que protocolse declare como @objc, lo que no quiero en una aplicación Swift pura.

Este código da un error de compilación ( weakno se puede aplicar al tipo que no sea de clase MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

Necesito prefijar el protocolo con @objc, luego funciona.

Pregunta: ¿Cuál es la forma 'pura' rápida de lograr a weak delegate?

hnh
fuente
tenga en cuenta ... stackoverflow.com/a/60837041/294884
Fattie

Respuestas:

1039

Debe declarar el tipo de protocolo como AnyObject.

protocol ProtocolNameDelegate: AnyObject {
    // Protocol stuff goes here
}

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Usando AnyObjectusted dice que solo las clases pueden cumplir con este protocolo, mientras que las estructuras o enumeraciones no pueden.

flainez
fuente
25
Mi problema con estas soluciones es que llamar al delegado causa un bloqueo: EXC_BAD_ACCESS (como lo han señalado otros en otro lugar). Esto parece ser un error. La única solución que he encontrado es usar @objc y eliminar todos los tipos de datos Swift del protocolo.
Jim T
12
¿Cuál es la forma correcta de hacer delegados débiles ahora en Swift? La documentación de Apple no muestra o declara al delegado como débil en su código de ejemplo: developer.apple.com/library/ios/documentation/swift/conceptual/…
C0D3
2
Esto no siempre es seguro: recuerde que solo necesita debilitar al delegado si también tiene una referencia al delegador y debe romper ese ciclo de referencia fuerte. Si el delegado no tiene ninguna referencia al delegador, el delegado puede salir del alcance (porque es débil) y tendrá bloqueos y otros problemas: / algo a tener en cuenta.
Trev14
55
Por cierto: creo que el "nuevo estilo" (Swift 5) es hacer protocol ProtocolNameDelegate: AnyObject, pero no importa.
hnh
1
Debería ser AnyObjectya classque quedará en desuso en algún momento.
José
283

Respuesta suplementaria

Siempre estaba confundido acerca de si los delegados deberían ser débiles o no. Recientemente, he aprendido más sobre los delegados y cuándo usar referencias débiles, así que permítanme agregar algunos puntos adicionales aquí por el bien de los futuros espectadores.

  • El propósito de usar la weakpalabra clave es evitar ciclos de referencia fuertes (retener ciclos). Los ciclos de referencia fuertes ocurren cuando dos instancias de clase tienen referencias fuertes entre sí. Sus recuentos de referencia nunca llegan a cero, por lo que nunca se desasignan.

  • Solo necesita usar weaksi el delegado es una clase. Las estructuras y enumeraciones rápidas son tipos de valor (sus valores se copian cuando se crea una nueva instancia), no tipos de referencia, por lo que no hacen ciclos de referencia fuertes .

  • weaklas referencias son siempre opcionales (de lo contrario lo usaría unowned) y siempre usan var(no let) para que se pueda establecer lo opcional nilcuando se desasigna.

  • Una clase principal debería tener una referencia fuerte a sus clases secundarias y, por lo tanto, no usar la weakpalabra clave. Sin embargo, cuando un niño quiere una referencia a su padre, debe hacer una referencia débil al usar la weakpalabra clave.

  • weakdebe usarse cuando desea una referencia a una clase que no es de su propiedad, no solo para un niño que hace referencia a su padre. Cuando dos clases no jerárquicas necesitan referenciarse entre sí, elija una para ser débil. El que elijas depende de la situación. Consulte las respuestas a esta pregunta para obtener más información al respecto.

  • Como regla general, los delegados deben marcarse comoweak porque la mayoría de los delegados hacen referencia a clases que no son de su propiedad. Esto es definitivamente cierto cuando un niño está usando un delegado para comunicarse con un padre. Usar una referencia débil para el delegado es lo que recomienda la documentación . (Pero mira esto también).

  • Los protocolos se pueden usar tanto para tipos de referencia (clases) como para tipos de valores (estructuras, enumeraciones). Entonces, en el caso probable de que necesite debilitar a un delegado, debe convertirlo en un protocolo de solo objeto. La forma de hacerlo es agregar AnyObjecta la lista de herencia del protocolo. (En el pasado hiciste esto usando la classpalabra clave, pero AnyObjectahora se prefiere ).

    protocol MyClassDelegate: AnyObject {
        // ...
    }
    
    class SomeClass {
        weak var delegate: MyClassDelegate?
    }

Estudio adicional

Leer los siguientes artículos es lo que me ayudó a entender esto mucho mejor. También discuten temas relacionados como la unownedpalabra clave y los ciclos de referencia fuertes que ocurren con los cierres.

Relacionado

Suragch
fuente
55
Todo esto es agradable e interesante, pero no está realmente relacionado con mi pregunta original, que no se trata de débil / ARC en sí ni de por qué los delegados suelen ser débiles. Ya sabemos sobre todo eso y nos preguntamos cómo puede declarar una referencia de protocolo débil (respondida perfectamente por @flainez).
hnh
30
Tienes razón. De hecho, tenía la misma pregunta que antes, pero me faltaba mucha información de fondo. Hice la lectura anterior e hice las notas complementarias para ayudarme a comprender todos los problemas relacionados con su pregunta. Ahora creo que puedo aplicar su respuesta aceptada y saber por qué lo estoy haciendo. Espero que tal vez también ayude a los futuros espectadores.
Suragch
55
¿Pero puedo tener un protocolo débil que NO depende del tipo? Un protocolo por sí mismo no le importa qué objeto se está conformando a sí mismo. Entonces, tanto una clase como una estructura pueden ajustarse a ella. ¿Es posible seguir teniendo el beneficio de que ambos puedan ajustarse a él, pero que solo los tipos de clase que conforman sean débiles?
FlowUI. SimpleUITesting.com
> como la mayoría de los delegados hacen referencia a clases que no son de su propiedad, reescribiría esto como: la mayoría de los delegadores. De lo contrario, el objeto no propiedad se convierte en el propietario
Victor Jalencas
36

AnyObject es la forma oficial de usar una referencia débil en Swift.

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

De Apple:

Para evitar ciclos de referencia fuertes, los delegados deben declararse como referencias débiles. Para obtener más información sobre referencias débiles, consulte Ciclos de referencia fuertes entre instancias de clase. Marcar el protocolo como solo de clase más tarde le permitirá declarar que el delegado debe usar una referencia débil. Usted marca un protocolo como de solo clase heredando de AnyObject , como se discute en Protocolos de solo clase.

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276

Tim Chen
fuente
77
Interesante. ¿Está classen desuso en Swift 4.1?
hnh
@hnh Todavía puede hacer un "pseudo-protocolo" convirtiéndolo en una clase, pero protocolo: AnyObject hace exactamente lo que el OP está pidiendo con menos efectos secundarios que convertirlo en una clase. (todavía no puede usar dicho protocolo con tipos de valor, pero declararlo como una clase tampoco resolverá eso)
Arru
8

Actualización: Parece que el manual se ha actualizado y el ejemplo al que me refería se ha eliminado. Vea la edición de la respuesta de @ flainez arriba.

Original: usar @objc es la forma correcta de hacerlo, incluso si no interopera con Obj-C. Asegura que su protocolo se aplique a una clase y no a una enumeración o estructura. Consulte "Comprobación de conformidad de protocolo" en el manual.

William Rust
fuente
Como se mencionó, esto es IMO, no una respuesta a la pregunta. Un programa Swift simple debería ser capaz de sostenerse por sí mismo sin estar atado al NS'ism (esto podría implicar que ya no se usarán los delegados sino alguna otra construcción de diseño). A mi pura Swift MyClass en realidad no le importa si el destino es una estructura u objeto, ni necesito opciones. Tal vez puedan arreglarlo más tarde, después de todo, es un nuevo idioma. ¿Posiblemente algo así como 'protocolo de clase XYZ' si se requiere semántica de referencia?
hnh
44
Creo que también vale la pena señalar que \ @objc tiene efectos secundarios adicionales: la sugerencia de NSObjectProtocol de @eXhausted es un poco mejor. Con \ @objc: si el delegado de clase toma un argumento de objeto, como 'handleResult (r: MySwiftResultClass)', ¡MySwiftResultClass ahora debe heredar de NSObject! Y presumiblemente ya no es espacio de nombres, etc. En resumen: \ @objc es una función de puente, no de idioma.
hnh
Creo que han resuelto esto. Ahora escribe: protocol MyClassDelegate: class {}
user3675131
¿Dónde está la documentación sobre esto? O estoy ciego o estoy haciendo algo mal, porque no puedo encontrar ninguna información sobre esto ... O_O
BastiBen
No estoy seguro de si responde a la pregunta del OP o no, pero esto es útil especialmente si interopera con Objc-C;)
Dan Rosenstark
-1

El protocolo debe ser subclase de AnyObject, clase

ejemplo dado a continuación

    protocol NameOfProtocol: class {
   // member of protocol
    }
   class ClassName: UIViewController {
      weak var delegate: NameOfProtocol? 
    }
Rakesh Kunwar
fuente
-9

Apple usa "NSObjectProtocol" en lugar de "clase".

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

Esto también funciona para mí y eliminó los errores que estaba viendo al intentar implementar mi propio patrón de delegado.

Michael Rose
fuente
55
No es relevante para la pregunta, esta pregunta se trata de construir una clase Swift pura (específicamente sin NSObject) que soporte un objeto delegado. No se trata de implementar protocolos Objective-C, que es lo que está haciendo. Este último requiere @objc, también conocido como NSObjectProtocol.
hnh
OK, pero no se recomienda.
DawnSong