J'ai un protocole RequestType et il a associType Model comme ci-dessous.
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
}
public extension RequestType {
public func executeRequest(completionHandler: Result<Model, NSError> -> Void) {
request.response(rootKeyPath: rootKeyPath) { [weak self] (response: Response<Model, NSError>) -> Void in
completionHandler(response.result)
guard let weakSelf = self else { return }
if weakSelf.logging { debugPrint(response) }
}
}
}
Maintenant, j'essaie de faire une file d'attente de toutes les demandes ayant échoué.
public class RequestEventuallyQueue {
static let requestEventuallyQueue = RequestEventuallyQueue()
let queue = [RequestType]()
}
Mais j’obtiens l’erreur sur la ligne let queue = [RequestType]()
que Protocol RequestType ne peut être utilisé que comme contrainte générique car il a les exigences Self ou relatedType.
Supposons pour le moment que nous ajustions votre protocole pour ajouter une routine utilisant le type associé:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
Et Swift devait vous permettre de créer un tableau de RequestType
comme vous le souhaitez. Je pourrais passer un tableau de ces types de demandes à une fonction:
func handleQueueOfRequests(queue: [RequestType]) {
// frobulate All The Things!
for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}
J'arrive au point que je veux faire toutes sortes de choses, mais j'ai besoin de savoir quel type d'argument passer dans l'appel. Certaines de mes entités RequestType
pourraient prendre un LegoModel
, d'autres un PlasticModel
et d'autres, du PeanutButterAndPeepsModel
. Swift n'est pas satisfait de l'ambiguïté, il ne vous laissera donc pas déclarer une variable d'un protocole associé à un type.
En même temps, il est parfaitement logique, par exemple, de créer un tableau de RequestType
quand on SAIT que tous utilisent le LegoModel
. Cela semble raisonnable, et c'est vrai, mais vous avez besoin d'un moyen de l'exprimer.
Une façon de faire est de créer une classe (ou une structure, ou une énumération) qui associe un type réel au nom du type de modèle abstrait:
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
Maintenant, il est tout à fait raisonnable de déclarer un tableau de LegoRequestType
parce que si nous voulions tous frobulate
nous savons qu'il nous faudrait passer un LegoModel
à chaque fois.
Cette nuance avec les types associés rend tout protocole spécial qui les utilise. La Swift) bibliothèque standard a des protocoles comme celui-ci, notamment Collection
ou Sequence
.
Pour vous permettre de créer un ensemble d'éléments implémentant le protocole Collection
ou un ensemble d'éléments implémentant le protocole de séquence, la bibliothèque standard utilise une technique appelée "effacement de type" pour créer les types de structure AnyCollection<T>
ou AnySequence<T>
. La technique de suppression de type est assez complexe à expliquer dans une réponse Stack Overflow, mais si vous effectuez une recherche sur le Web, vous trouverez de nombreux articles à ce sujet.
Je peux recommander une vidéo de Alex Gallagher sur Protocoles avec types associés (PAT) sur YouTube.
Un petit changement dans la conception de votre code pourrait rendre cela possible. Ajoutez un protocole vide, non associé, au sommet de votre hiérarchie de protocoles. Comme ça...
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'
}
Un autre exemple, avec des classes dérivées du protocole RequestType, créant une file d'attente et la transmettant à une fonction pour imprimer le type approprié
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)