Supongamos por el momento que ajustamos su protocolo para agregar una rutina que usa el tipo asociado:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
Y Swift te permitiría crear una matriz de RequestType
la forma que quieras. Podría pasar una matriz de esos tipos de solicitud a una función:
func handleQueueOfRequests(queue: [RequestType]) {
for request in queue {
request.frobulateModel()
}
}
Llego al punto en que quiero frobular todas las cosas, pero necesito saber qué tipo de argumento pasar a la llamada. Algunas de mis RequestType
entidades podrían tomar una LegoModel
, otras podrían tomar una PlasticModel
y otras podrían tomar una PeanutButterAndPeepsModel
. Swift no está contento con la ambigüedad, por lo que no le permitirá declarar una variable de un protocolo que tenga un tipo asociado.
Al mismo tiempo, tiene mucho sentido, por ejemplo, crear una matriz de RequestType
cuando SABEMOS que todos usan el LegoModel
. Esto parece razonable, y lo es, pero necesitas alguna forma de expresarlo.
Una forma de hacerlo es crear una clase (o estructura o enumeración) que asocie un tipo real con el nombre del tipo de modelo abstracto:
class LegoRequestType: RequestType {
typealias Model = LegoModel
}
Ahora es completamente razonable declarar una matriz de LegoRequestType
porque si quisiéramos frobulate
todas, sabemos que tendríamos que pasar una LegoModel
cada vez.
Este matiz con los tipos asociados hace que cualquier protocolo que los utilice sea especial. La biblioteca estándar de Swift tiene protocolos como este más notablemente Collection
o Sequence
.
Para permitirle crear una serie de cosas que implementan el Collection
protocolo o un conjunto de cosas que implementan el protocolo de secuencia, la Biblioteca estándar emplea una técnica llamada "borrado de tipo" para crear los tipos de estructura AnyCollection<T>
o AnySequence<T>
. La técnica de borrado de tipo es bastante compleja de explicar en una respuesta de Stack Overflow, pero si busca en la web, hay muchos artículos al respecto.
Puedo recomendar un video de Alex Gallagher sobre protocolos con tipos asociados (PAT) en YouTube.
Desde Swift 5.1 - Xcode 11
Puede usar un tipo de resultado opaco para lograr algo así.
imagina esto:
protocol ProtocolA { associatedtype number } class ClassA: ProtocolA { typealias number = Double }
Entonces lo siguiente genera el error:
var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */
Pero hacer que el tipo sea opaco agregando la
some
palabra clave antes del tipo solucionará el problema y, por lo general, eso es lo único que queremos:var objectA: some ProtocolA = ClassA()
fuente
Un pequeño cambio en el diseño de su código podría hacerlo posible. Agregue un protocolo vacío, non-associatedType, en la parte superior de su jerarquía de protocolos. Me gusta esto...
public protocol RequestTypeBase: class{} public protocol RequestType: RequestTypeBase { associatedtype Model var path: Model? { get set } //Make it type of Model } public class RequestEventuallyQueue { static let requestEventuallyQueue = RequestEventuallyQueue() var queue = [RequestTypeBase]() //This has to be 'var' not 'let' }
Otro ejemplo, con clases derivadas del protocolo RequestType, haciendo una cola y pasando la cola a una función para imprimir el tipo apropiado
public class RequestA<AType>: RequestType{ public typealias Model = AType public var path: AType? } public class RequestB<BType>: RequestType{ public typealias Model = BType public var path: BType? } var queue = [RequestTypeBase]() let aRequest: RequestA = RequestA<String>() aRequest.path = "xyz://pathA" queue.append(aRequest) let bRequest: RequestB = RequestB<String>() bRequest.path = "xyz://pathB" queue.append(bRequest) let bURLRequest: RequestB = RequestB<URL>() bURLRequest.path = URL(string: "xyz://bURLPath") queue.append(bURLRequest) func showFailed(requests: [RequestTypeBase]){ for request in requests{ if let request = request as? RequestA<String>{ print(request.path!) }else if let request = request as? RequestB<String>{ print(request.path!) }else if let request = request as? RequestB<URL>{ print(request.path!) } } } showFailed(requests: queue)
fuente
Swift 5.1
Un ejemplo de cómo puede usar protocolos genéricos implementando un tipo asociado y un protocolo base :
import Foundation protocol SelectOptionDataModelProtocolBase: class{} protocol SelectOptionDataModelProtocol: SelectOptionDataModelProtocolBase { associatedtype T var options: Array<T> { get } var selectedIndex: Int { get set } } class SelectOptionDataModel<A>: SelectOptionDataModelProtocol { typealias T = A var options: Array<T> var selectedIndex: Int init(selectedIndex _selectedIndex: Int, options _options: Array<T>) { self.options = _options self.selectedIndex = _selectedIndex } }
Y un ejemplo de controlador de vista:
import UIKit struct Car { var name: String? var speed: Int? } class SelectOptionViewController: UIViewController { // MARK: - IB Outlets // MARK: - Properties var dataModel1: SelectOptionDataModelProtocolBase? var dataModel2: SelectOptionDataModelProtocolBase? var dataModel3: SelectOptionDataModelProtocolBase? // MARK: - Initialisation required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } convenience init() { self.init(title: "Settings ViewController") } init(title _title: String) { super.init(nibName: nil, bundle: nil) self.title = _title self.dataModel1 = SelectOptionDataModel<String>(selectedIndex: 0, options: ["option 1", "option 2", "option 3"]) self.dataModel2 = SelectOptionDataModel<Int>(selectedIndex: 0, options: [1, 2, 3]) self.dataModel3 = SelectOptionDataModel<Car>(selectedIndex: 0, options: [Car(name: "BMW", speed: 90), Car(name: "Toyota", speed: 60), Car(name: "Subaru", speed: 120)]) } // MARK: - IB Actions // MARK: - View Life Cycle }
fuente
Este error también puede ocurrir en el siguiente escenario:
protocol MyProtocol { assosciatedtype SomeClass func myFunc() -> SomeClass } struct MyStuct { var myVar = MyProtocol }
En este caso, todo lo que tiene que hacer para solucionar el problema es usar genéricos:
protocol MyProtocol { assosciatedtype SomeClass func myFunc() -> SomeClass } struct MyStuct<T: MyProtocol> { var myVar = T }
fuente