swift swift4

swift - Obligado a emitir, incluso si el protocolo requiere un tipo dado



swift4 (5)

Actualización de Swift 5

En Swift 5 (Xcode 10.2), su código ahora funciona como se esperaba sin tener que realizar un lanzamiento forzado.

En Swift 4.x, Swift no admite completamente las restricciones de superclase en los protocolos, es decir, poder definir el protocol P where Self : C donde C es el tipo de una clase.

El hecho de que el compilador no le impida hacer esto hasta que la característica se implemente realmente fue un descuido, como dijo el ingeniero del compilador de Swift Slava Pestov:

Slava Pestov agregó un comentario - 31 mayo 2018 1:19 PM

[...] "protocolo P: Foo donde Self: Class" fue descubierto por accidente por los usuarios, y realmente no funciona por completo. Fue un descuido que no fue prohibido.

Sin embargo, esta es una característica que está destinada a implementarse completamente en una versión futura del lenguaje como parte de SE-0156 .

Slava Pestov agregó un comentario - 31 mayo 2018 1:19 PM

Se supone que ambos funcionan, pero aún no hemos implementado completamente la propuesta.

( Editar : Slava ahora ha implementado esto en #17611 , #17651 , #17816 y #17851 , por lo que los obtendrá en Swift 5, disponible en Xcode 10.2)

Una vez implementado, podrá tratar un tipo de protocolo como el tipo de clase del que se requieren tipos conformes para heredar (por ejemplo, permitiéndole tratar su Fooable como un UIViewController sin tener que emitir), de la misma manera que puede tratar una clase existencial como Fooable & UIViewController como un UIViewController .

No solo eso, sino que también podrá establecer el requisito de superclase directamente en el protocolo en lugar de en una cláusula where , por ejemplo:

protocol Fooable : UIViewController { func foo() }

Sin embargo, hasta Swift 5, recomendaría alejarse de los protocolos restringidos de superclase: actualmente tienen algunos bordes ásperos desagradables a su alrededor.

Por ejemplo, esto se compilará mal y se bloqueará en tiempo de ejecución en Swift 4.1:

class C : P { func speak() {} } protocol P where Self : C { func speak() } let c: P = C() c.speak()

y bloqueará el compilador en versiones posteriores del lenguaje ( SR-6816 ).

Como solución alternativa, puede usar un protocolo subrayado con una clase de tipos de letra existenciales para hacer cumplir la restricción de clase. Por ejemplo:

import UIKit protocol _Fooable : class { func foo() } typealias Fooable = _Fooable & UIViewController class SampleViewController : Fooable /* implicitly : UIViewController */ { func foo() { print("foo") } } // ... let vc1: Fooable = SampleViewController() let vc2: Fooable = SampleViewController() vc1.show(vc2, sender: nil)

Tengo el siguiente código:

import UIKit protocol Fooable: class where Self: UIViewController { func foo() } class SampleViewController: UIViewController, Fooable { func foo() { print("foo") } } let vc1: Fooable = SampleViewController() let vc2: Fooable = SampleViewController() // vc1.show(vc2, sender: nil) - error: Value of type ''Fooable'' has no member ''show'' // (vc1 as! UIViewController).show(vc2, sender: nil) - error: Cannot convert value of type ''Fooable'' to expected argument type ''UIViewController'' (vc1 as! UIViewController).show((vc2 as! UIViewController), sender: nil)

las líneas comentadas no se compilan.

¿Por qué me veo obligado a emitir un objeto de tipo de protocolo a UIViewController incluso si el protocolo Fooable requiere, los tipos que se ajustan a él heredan de UIViewController ?


El patrón común es hacerlo así:

protocol Fooable { func foo() var viewController: UIViewController } class SampleViewController: UIViewController, Fooable { func foo() { print("foo") } var viewController: UIViewController { return self } }

En Swift 4 puede hacer vars del tipo UIViewController & Fooable . En Swift 3 usa el truco anterior.


En primer lugar, el requisito de clase es redundante aquí ya que su protocolo requiere que cualquier Fooable extienda UIViewController, que es una clase.

En segundo lugar, esto se siente como una especie de supervisión por parte del equipo de Swift porque esto funciona a pesar de que todo lo que doStuff sabe sobre sus argumentos es que implementan Fooable, lo que sugiere que su código debería funcionar :

class Strawman { let name: String public func bar(_ x: Strawman) { print("/(name) bars /(x.name) from entering.") } public init(name: String) { self.name = name } } protocol Fooable where Self: Strawman { func foo() } class StrawFooable: Strawman, Fooable { public func foo() { print("Foo!") } } let sm1 = StrawFooable(name: "Strawman1") let sm2 = StrawFooable(name: "Strawman2") // This will not compile if you define doStuff as // func doStuff(with x: Fooable, and y: Fooable) { func doStuff<T: Fooable>(with x: T, and y: T) { x.bar(y) x.foo() y.bar(x) y.foo() } // This will not compile if you annotate sm1 and sm2 as Fooable. doStuff(with: sm1, and: sm2)

¿Mi recomendación? Presentar un informe de error.

PD. ¡Como WTF adicional si agrega conformidad a la clase base con una extensión, el compilador se bloquea! Quiero decir, no tiene mucho sentido hacerlo, pero realmente no debería estar bloqueando el compilador.


La adopción del protocolo Fooable le dice al compilador que este UIViewController particular responde a foo() , nada menos.

En conclusión inversa, Fooable no se convierte necesariamente en UIViewController .

La restricción Self: UIViewController es solo otra información para que el compilador se queje en tiempo de compilación si la clase afectada no es UIViewController

En su caso, cuando SampleViewController en Fooable el compilador solo sabe que SampleViewController responde a foo() . No sabe que el tipo es en realidad una subclase de UIViewController .

Por lo tanto, no anote una clase concreta en un protocolo si desea acceder a las propiedades de la clase concreta.

Sin embargo, puede agregar el método show y otras propiedades / métodos comunes al protocolo

protocol Fooable: class where Self: UIViewController { func foo() func show(_ vc: Fooable, sender: Any?) }

entonces puede usar Fooable porque el compilador sabe que el tipo que adopta el protocolo responde al método.

Una práctica adecuada para anotar un tipo en un protocolo es, por ejemplo, cuando va a crear un tipo de colección heterogéneo pero restringido

let array : [CustomStringConvertible] = ["Foo", 1, false] array.forEach{ print("/($0)")}

El código imprime los tres elementos utilizando la propiedad de description que responden todos los elementos. El compilador reconoce los tres elementos como tipos que tienen una propiedad de description , no como String , Int y Bool .

Actualizar:

En Swift 5 se implementa el soporte de protocolos restringidos de superclase.


No necesita emitir a un objeto de tipo Fooable cuando Fooable una instancia de su controlador de vista. Los siguientes trabajos:

import UIKit protocol Fooable: class where Self: UIViewController { func foo() } class SampleViewController: UIViewController, Fooable { func foo() { print("foo") } } let vc1 = SampleViewController() let vc2 = SampleViewController() vc1.show(vc2, sender: nil)

Cualquier clase puede implementar este protocolo, pero solo UIViewController tendrá disponible el método func foo() .