En Swift puedo establecer explícitamente el tipo de una variable declarándola 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<ProtocolOne,ProtocolTwo>//etc
¿Qué sucede si me gustaría 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<ABCProtocolOne,ABCProtocolTwo> * object = ...;
En Swift, esperaría que se vea así:
var object: TYPE_NAME,ProtocolOne//etc
Esto nos da la flexibilidad de poder lidiar con la implementación del tipo base, así como con la interfaz agregada definida en el protocolo.
¿Hay otra forma más obvia en la que podría estar perdiendo?
Ejemplo
Como ejemplo, digamos que tengo una UITableViewCell
fábrica que se encarga de devolver las células de acuerdo con un protocolo. Podemos configurar fácilmente una función genérica que devuelva celdas que se ajusten a un protocolo:
class CellFactory {
class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
//etc
}
}
más adelante quiero quitar estas celdas mientras aprovecho tanto el tipo como 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 a la MyProtocol
declaración de la variable.
Justificación
Si está familiarizado con Factory Pattern, esto tendría sentido en el contexto de poder devolver objetos de una clase en particular que implementan 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.
Si bien el tipo suministrado no se ajusta exactamente a la interfaz mencionada, el objeto que devuelve la fábrica sí lo hace y, por lo tanto, me gustaría la flexibilidad para interactuar tanto con el tipo de clase base como con la interfaz de protocolo declarada
fuente
NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;
. Este objeto parece bastante inútil porqueNSSomething
ya sabe a qué se ajusta. Si no se ajusta a uno de los protocolos<>
, obtendráunrecognised selector ...
bloqueos. Esto no proporciona ningún tipo de seguridad.Respuestas:
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.
Para hacer una variable opcional:
o como parámetro de un método:
Apple anunció esto en la WWDC 2017 en la Sesión 402: Novedades de Swift
fuente
No puedes declarar variables como
ni declarar el tipo de retorno de la función como
Puede declarar como un parámetro de función como este, pero es básicamente una conversión.
A partir de ahora, todo lo que puede hacer es como:
Con esto, técnicamente
cell
es idéntico aasProtocol
.Pero, en cuanto al compilador,
cell
tiene una interfaz deUITableViewCell
solo, mientras queasProtocol
solo tiene una interfaz de protocolos. Entonces, cuando quiera llamar aUITableViewCell
los métodos, debe usarcell
variable. Cuando desee llamar al método de protocolos, useasProtocol
variable.Si está seguro de que la celda se ajusta a los protocolos, no es necesario que utilice
if let ... as? ... {}
. me gusta:fuente
-> UITableViewCell<MyProtocol>
, esto no es válido, porqueUITableViewCell
no es un tipo genérico. Creo que esto ni siquiera se compila.cell
solo tiene métodos de protocolos (para el compilador).Desafortunadamente, Swift no admite la conformidad del protocolo a nivel de objeto. Sin embargo, hay una solución un tanto incómoda que puede servir para sus propósitos.
Luego, en cualquier lugar donde necesite hacer cualquier cosa que tenga UIViewController, accedería al aspecto .viewController de la estructura y cualquier cosa que necesite el aspecto del protocolo, haría referencia al .protocol.
Por ejemplo:
Ahora, cada vez que necesite mySpecialViewController para hacer algo relacionado con UIViewController, simplemente haga referencia a mySpecialViewController.viewController y siempre que lo necesite para realizar alguna función de protocolo, haga referencia a mySpecialViewController.protocol.
Con suerte, Swift 4 nos permitirá declarar un objeto con protocolos adjuntos en el futuro. Pero por ahora esto funciona.
¡Espero que esto ayude!
fuente
Tal vez me equivoque, pero ¿no estás hablando de agregar conformidad con el protocolo a la
UITableCellView
clase? En ese caso, el protocolo se extiende a la clase base y no al objeto. Consulte la documentación de Apple sobre cómo declarar la adopción de protocolos con una extensión, que en su caso sería algo como:Además de la documentación de Swift ya mencionada, consulte el artículo de Nate Cooks Funciones genéricas para tipos incompatibles con más ejemplos.
La adopción de protocolo hará precisamente 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 determinado no sabe nada fuera del protocolo. Pero esto puede evitarse definiendo un protocolo que tenga todos los métodos / variables / ...
Si desea que un método genérico, una variable se ajuste tanto a un protocolo como a los tipos de clase base, es posible que no tenga suerte. Pero parece que necesita definir el protocolo lo suficientemente amplio para tener los métodos de conformidad necesarios y, al mismo tiempo, lo suficientemente estrecho como para tener la opción de adoptarlo en las clases base sin demasiado trabajo (es decir, simplemente declarando que una clase se ajusta a la protocolo).
fuente
Una vez tuve una situación similar al intentar vincular mis conexiones interactor genéricas en Storyboards (IB no le permitirá conectar puntos de venta a protocolos, solo instancias de objetos), que solucioné simplemente enmascarando el ivar público de la clase base con un ivar privado calculado propiedad. Si bien esto no evita que alguien realice asignaciones ilegales per se, proporciona una forma conveniente de evitar de forma segura cualquier interacción no deseada con una instancia no conforme en tiempo de ejecución. (es decir, evitar llamar a métodos delegados a objetos que no se ajustan al protocolo).
Ejemplo:
El "outputReceiver" se declara opcional, al igual que el "protocolOutputReceiver" privado. Al acceder siempre al outputReceiver (también conocido como delegado) a través de este último (la propiedad calculada), filtro de manera efectiva cualquier objeto que no se ajuste al protocolo. Ahora puedo simplemente usar el encadenamiento opcional para llamar de manera segura al objeto delegado ya sea que implemente el protocolo o incluso que exista.
Para aplicar esto a su situación, puede hacer que el ivar público sea del tipo "YourBaseClass?" (a diferencia de AnyObject) y utilice la propiedad computada privada para hacer cumplir la conformidad del protocolo. FWIW.
fuente