En Swift, ¿cómo puedo declarar una variable de un tipo específico que se ajuste a uno o más protocolos?

En Swift puedo establecer explícitamente el tipo de una variable al declararla de la siguiente manera:

var object: TYPE_NAME 

Si queremos dar un paso más y declarar una variable que se ajuste a múltiples protocolos, podemos usar el protocol declarativo:

 var object: protocol//etc 

¿Qué ocurre si deseo declarar un objeto que se ajusta a uno o más protocolos y también es de un tipo de clase base específico? El equivalente de Objective-C se vería así:

 NSSomething * object = ...; 

En Swift esperaría que se vea así:

 var object: TYPE_NAME,ProtocolOne//etc 

Esto nos da la flexibilidad de poder tratar con la implementación del tipo de base así como la interfaz agregada definida en el protocolo.

¿Hay alguna otra forma más obvia que pueda estar perdiendo?

Ejemplo

Como ejemplo, digamos que tengo una fábrica UITableViewCell que es responsable de devolver las celdas conforme a un protocolo. Podemos configurar fácilmente una función genérica que devuelva células que se ajusten a un protocolo:

 class CellFactory { class func createCellForItem(item: SpecialItem,tableView: UITableView) -> T { //etc } } 

más tarde deseo dequeue estas células mientras aprovecho el tipo y el protocolo

 var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell 

Esto devuelve un error porque una celda de vista de tabla no se ajusta al protocolo …

Me gustaría poder especificar que la celda es una UITableViewCell y se ajusta al MyProtocol en la statement de la variable?

Justificación

Si está familiarizado con el patrón de fábrica, esto tendría sentido en el contexto de poder devolver objetos de una clase en particular que implementa una determinada interfaz.

Al igual que en mi ejemplo, a veces nos gusta definir interfaces que tienen sentido cuando se aplican a un objeto en particular. Mi ejemplo de la celda de vista de tabla es una de esas justificaciones.

Mientras que el tipo suministrado no se ajusta exactamente a la interfaz mencionada, el objeto que devuelve la fábrica sí lo hace y me gustaría la flexibilidad para interactuar tanto con el tipo de clase base como con la interfaz de protocolo declarada

En Swift 4 ahora es posible declarar una variable que es una subclase de un tipo e implementa uno o más protocolos al mismo tiempo.

 var myVariable: MyClass & MyProtocol & MySecondProtocol 

o como el parámetro de un método:

 func shakeEm(controls: [UIControl & Shakeable]) {} 

Apple anunció esto en la WWDC 2017 en la sesión 402: ¿Qué hay de nuevo en Swift?

En segundo lugar, quiero hablar sobre la composición de clases y protocolos. Por lo tanto, aquí he presentado este protocolo de Shakable para un elemento UI que puede dar un pequeño efecto de sacudida para llamar la atención sobre sí mismo. Y he avanzado y ampliado algunas de las clases de UIKit para proporcionar realmente esta funcionalidad de batido. Y ahora quiero escribir algo que parece simple. Solo quiero escribir una función que requiera un conjunto de controles que se puedan mover y agite los que están habilitados para llamar la atención sobre ellos. ¿Qué tipo puedo escribir aquí en este conjunto? En realidad es frustrante y complicado. Entonces, podría intentar usar un control de UI. Pero no todos los controles de UI se pueden mover en este juego. Podría intentar shakable, pero no todos los shakables son controles UI. Y en realidad no hay una buena manera de representar esto en Swift 3. Swift 4 introduce la noción de componer una clase con cualquier cantidad de protocolos.

No puedes declarar una variable como

 var object:Base,protocol = ... 

ni declarar el tipo de devolución de función como

 func someFunc() -> Base,protocol { ... } 

Puede declarar como un parámetro de función como este, pero básicamente se trata de un up-casting.

 func someFunc>(val:T) { // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2` } class SubClass:BaseClass, MyProtocol1, MyProtocol2 { //... } let val = SubClass() someFunc(val) 

A partir de ahora, todo lo que puedes hacer es como:

 class CellFactory { class func createCellForItem(item: SpecialItem) -> UITableViewCell { return ... // any UITableViewCell subclass } } let cell = CellFactory.createCellForItem(special) if let asProtocol = cell as? protocol { asProtocol.protocolMethod() cell.cellMethod() } 

Con esto, técnicamente cell es idéntico a asProtocol .

Pero, en cuanto al comstackdor, la cell tiene una interfaz de UITableViewCell solamente, mientras que asProtocol solo tiene una interfaz de protocolos. Entonces, cuando quiera llamar a los métodos de UITableViewCell , debe usar cell variable de cell . Cuando desee llamar al método de protocolos, use asProtocol variable de asProtocol .

Si está seguro de que la celda cumple con los protocolos, no tiene que usarla if let ... as? ... {} if let ... as? ... {} . me gusta:

 let cell = CellFactory.createCellForItem(special) let asProtocol = cell as protocol 

Desafortunadamente, Swift no es compatible con la conformidad del protocolo de nivel de objeto. Sin embargo, hay una solución alternativa algo incómoda que puede servir para sus propósitos.

 struct VCWithSomeProtocol { let protocol: SomeProtocol let viewController: UIViewController init(vc: T) where T: SomeProtocol { self.protocol = vc self.viewController = vc } } 

Entonces, en cualquier lugar que necesite hacer algo que tenga UIViewController, accederá al aspecto .viewController de la estructura y cualquier cosa que necesite el aspecto del protocolo, usted haría referencia al .protocol.

Por ejemplo:

 class SomeClass { let mySpecialViewController: VCWithSomeProtocol init(injectedViewController: T) where T: SomeProtocol { self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController) } } 

Ahora, cada vez que necesite mySpecialViewController para hacer cualquier cosa relacionada con UIViewController, solo hace referencia a mySpecialViewController.viewController y siempre que lo necesite para realizar alguna función de protocolo, hace referencia a mySpecialViewController.protocol.

Es de esperar que Swift 4 nos permita declarar un objeto con protocolos adjuntos en el futuro. Pero por ahora, esto funciona.

¡Espero que esto ayude!

EDITAR: Estaba equivocado , pero si alguien más leyó este malentendido como yo, dejo esta respuesta por ahí. El OP preguntó acerca de verificar la conformidad del protocolo del objeto de una determinada subclase, y esa es otra historia, como lo muestra la respuesta aceptada. Esta respuesta habla sobre la conformidad del protocolo para la clase base.

Tal vez estoy equivocado, pero ¿no estás hablando de agregar la conformidad del protocolo a la clase UITableCellView ? El protocolo se extiende en ese caso a la clase base, y no al objeto. Consulte la documentación de Apple sobre Cómo declarar la adopción del protocolo con una extensión que en su caso sería algo así como:

 extension UITableCellView : ProtocolOne {} // Or alternatively if you need to add a method, protocolMethod() extension UITableCellView : ProcotolTwo { func protocolTwoMethod() -> String { return "Compliant method" } } 

Además de la documentación Swift ya mencionada, vea también Nate Cooks article Funciones genéricas para tipos incompatibles con más ejemplos.

Esto nos da la flexibilidad de poder tratar con la implementación del tipo de base así como la interfaz agregada definida en el protocolo.

¿Hay alguna otra forma más obvia que pueda estar perdiendo?

La adopción del protocolo hará justamente esto, hacer que un objeto se adhiera al protocolo dado. Sin embargo, tenga en cuenta el lado adverso, que una variable de un tipo de protocolo dado no sabe nada fuera del protocolo. Pero esto puede eludirse definiendo un protocolo que tenga todos los métodos / variables / …

Mientras que el tipo suministrado no se ajusta exactamente a la interfaz mencionada, el objeto que devuelve la fábrica sí lo hace y me gustaría la flexibilidad para interactuar tanto con el tipo de clase base como con la interfaz de protocolo declarada

Si desea un método genérico, variable para ajustarse tanto a un protocolo como a tipos de clase base, puede que no tenga suerte. Pero parece que necesita definir el protocolo lo suficientemente amplio como para tener los métodos de conformidad necesarios, y al mismo tiempo lo suficientemente estrecho como para tener la opción de adoptarlo a las clases base sin demasiado trabajo (es decir, simplemente declarar que una clase se ajusta al protocolo).

Una vez tuve una situación similar cuando traté de vincular mis conexiones interactor genéricas en Storyboards (IB no te permitirá conectar tomas de stream a protocolos, solo instancias de objetos), lo que obtuve simplemente enmascarando la clase base ivar pública con un computador privado propiedad. Si bien esto no impide que alguien realice asignaciones ilegales per se, sí proporciona una manera conveniente de prevenir de forma segura cualquier interacción no deseada con una instancia no conforme en tiempo de ejecución. (es decir, evitar invocar métodos delegates a objetos que no se ajusten al protocolo).

Ejemplo:

 @objc protocol SomeInteractorInputProtocol { func getSomeString() } @objc protocol SomeInteractorOutputProtocol { optional func receiveSomeString(value:String) } @objc class SomeInteractor: NSObject, SomeInteractorInputProtocol { @IBOutlet var outputReceiver : AnyObject? = nil private var protocolOutputReceiver : SomeInteractorOutputProtocol? { get { return self.outputReceiver as? SomeInteractorOutputProtocol } } func getSomeString() { let aString = "This is some string." self.protocolOutputReceiver?.receiveSomeString?(aString) } } 

El “outputReceiver” se declara opcional, al igual que el “protocolOutputReceiver” privado. Al acceder siempre al outputReceiver (también conocido como delegate) a través de este último (la propiedad calculada), de hecho filtrar los objetos que no se ajustan al protocolo. Ahora puedo simplemente usar el encadenamiento opcional para llamar de manera segura al objeto delegado, implemente o no el protocolo o incluso exista.

Para aplicar esto a su situación, puede hacer que el público sea del tipo “YourBaseClass”. (a diferencia de AnyObject) y usa la propiedad privada computada para aplicar la conformidad del protocolo. FWIW.