ios - programacion - tipos de datos en swift
Uso de protocolos como tipos de matriz y parĂ¡metros de funciones en swift (6)
Quiero crear una clase que pueda almacenar objetos que se ajusten a un protocolo determinado. Los objetos deben almacenarse en una matriz de tipo. Según la documentación de Swift, los protocolos se pueden usar como tipos:
Como es un tipo, puede usar un protocolo en muchos lugares donde se permiten otros tipos, incluidos:
- Como tipo de parámetro o tipo de retorno en una función, método o inicializador
- Como el tipo de una constante, variable o propiedad
- Como el tipo de elementos en una matriz, diccionario u otro contenedor
Sin embargo, lo siguiente genera errores de compilación:
El protocolo ''SomeProtocol'' solo se puede usar como una restricción genérica porque tiene requisitos de tipo propio o asociado
¿Cómo se supone que resolver esto?
protocol SomeProtocol: Equatable {
func bla()
}
class SomeClass {
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol) {
self.protocols.append(element)
}
func removeElement(element: SomeProtocol) {
if let index = find(self.protocols, element) {
self.protocols.removeAtIndex(index)
}
}
}
Considero que su objetivo principal es mantener una colección de objetos que se ajusten a algún protocolo, agregar a esta colección y eliminarla de ella. Esta es la funcionalidad tal como se establece en su cliente, "SomeClass". La herencia equivalente se necesita y no es necesaria para esta funcionalidad. Podríamos haber hecho que esto funcione en matrices en Obj-C usando la función de "índice" que puede tomar un comparador personalizado, pero esto no es compatible con Swift. Entonces, la solución más simple es usar un diccionario en lugar de una matriz como se muestra en el siguiente código. He proporcionado getElements () que le devolverá la matriz de protocolos que deseaba. Entonces cualquiera que use SomeClass ni siquiera sabría que se usó un diccionario para la implementación.
Como en cualquier caso, necesitarías alguna propiedad distintiva para separar tus objetos, he asumido que es "nombre". Asegúrese de hacer su elemento element.name = "foo" cuando cree una nueva instancia de SomeProtocol. Si el nombre no está establecido, aún puede crear la instancia, pero no se agregará a la colección y addElement () devolverá "false".
protocol SomeProtocol {
var name:String? {get set} // Since elements need to distinguished,
//we will assume it is by name in this example.
func bla()
}
class SomeClass {
//var protocols = [SomeProtocol]() //find is not supported in 2.0, indexOf if
// There is an Obj-C function index, that find element using custom comparator such as the one below, not available in Swift
/*
static func compareProtocols(one:SomeProtocol, toTheOther:SomeProtocol)->Bool {
if (one.name == nil) {return false}
if(toTheOther.name == nil) {return false}
if(one.name == toTheOther.name!) {return true}
return false
}
*/
//The best choice here is to use dictionary
var protocols = [String:SomeProtocol]()
func addElement(element: SomeProtocol) -> Bool {
//self.protocols.append(element)
if let index = element.name {
protocols[index] = element
return true
}
return false
}
func removeElement(element: SomeProtocol) {
//if let index = find(self.protocols, element) { // find not suported in Swift 2.0
if let index = element.name {
protocols.removeValueForKey(index)
}
}
func getElements() -> [SomeProtocol] {
return Array(protocols.values)
}
}
Desea crear una clase genérica, con una restricción de tipo que requiera que las clases utilizadas con ella se ajusten a SomeProtocol
, como esta:
class SomeClass<T: SomeProtocol> {
typealias ElementType = T
var protocols = [ElementType]()
func addElement(element: ElementType) {
self.protocols.append(element)
}
func removeElement(element: ElementType) {
if let index = find(self.protocols, element) {
self.protocols.removeAtIndex(index)
}
}
}
En Swift hay una clase especial de protocolos que no proporciona polimorfismo sobre los tipos que lo implementan. Dichos protocolos utilizan palabras clave Equatable
o associatedtype
en sus definiciones (y Equatable
es una de ellas).
En algunos casos, es posible usar un envoltorio borrado por tipo para hacer que su colección sea homomorfa. A continuación hay un ejemplo.
// This protocol doesn''t provide polymorphism over the types which implement it.
protocol X: Equatable {
var x: Int { get }
}
// We can''t use such protocols as types, only as generic-constraints.
func ==<T: X>(a: T, b: T) -> Bool {
return a.x == b.x
}
// A type-erased wrapper can help overcome this limitation in some cases.
struct AnyX {
private let _x: () -> Int
var x: Int { return _x() }
init<T: X>(_ some: T) {
_x = { some.x }
}
}
// Usage Example
struct XY: X {
var x: Int
var y: Int
}
struct XZ: X {
var x: Int
var z: Int
}
let xy = XY(x: 1, y: 2)
let xz = XZ(x: 3, z: 4)
//let xs = [xy, xz] // error
let xs = [AnyX(xy), AnyX(xz)]
xs.forEach { print($0.x) } // 1 3
Ha accedido a una variante de un problema con los protocolos en Swift para los que aún no existe una buena solución.
Consulte también Ampliación de matriz para verificar si está ordenada en Swift. , contiene sugerencias sobre cómo solucionarlo que pueden ser adecuadas para su problema específico (su pregunta es muy genérica, quizás pueda encontrar una solución con estas respuestas).
La solución es bastante simple:
protocol SomeProtocol {
func bla()
}
class SomeClass {
init() {}
var protocols = [SomeProtocol]()
func addElement<T: SomeProtocol where T: Equatable>(element: T) {
protocols.append(element)
}
func removeElement<T: SomeProtocol where T: Equatable>(element: T) {
protocols = protocols.filter {
if let e = $0 as? T where e == element {
return false
}
return true
}
}
}
La solución limitada que encontré es marcar el protocolo como un protocolo de solo clase. Esto le permitirá comparar objetos usando el operador ''===''. Entiendo que esto no funcionará para las estructuras, etc., pero fue lo suficientemente bueno en mi caso.
protocol SomeProtocol: class {
func bla()
}
class SomeClass {
var protocols = [SomeProtocol]()
func addElement(element: SomeProtocol) {
self.protocols.append(element)
}
func removeElement(element: SomeProtocol) {
for i in 0...protocols.count {
if protocols[i] === element {
protocols.removeAtIndex(i)
return
}
}
}
}