web-dev-qa-db-fra.com

Le protocole ne peut être utilisé que comme contrainte générique, car il a les exigences Self ou type associé

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.

66
Rahul Katariya

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.

109
Scott Thompson

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)
1
Farhan Arshad