Il protocollo può essere utilizzato solo come vincolo generico perché ha requisiti Self o associatedType


102

Ho un protocollo RequestType e ha un modello di tipo associato come di seguito.

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) }
        }
    }

}

Ora sto cercando di fare una coda di tutte le richieste non riuscite.

public class RequestEventuallyQueue {

    static let requestEventuallyQueue = RequestEventuallyQueue()
    let queue = [RequestType]()

}

Ma ottengo l'errore in linea let queue = [RequestType]()che Protocol RequestType può essere utilizzato solo come vincolo generico perché ha requisiti Self o associatedType.

Risposte:


152

Supponiamo per il momento di modificare il tuo protocollo per aggiungere una routine che utilizza il tipo associato:

public protocol RequestType: class {
    associatedtype Model
    var path: String { get set }

    func frobulateModel(aModel: Model)
}

E Swift ti consentiva di creare una matrice RequestTypenel modo in cui desideri. Potrei passare un array di quei tipi di richiesta in una funzione:

func handleQueueOfRequests(queue: [RequestType]) {
    // frobulate All The Things!

    for request in queue {
       request.frobulateModel(/* What do I put here? */)
    }
}

Arrivo al punto che voglio frobulare tutte le cose, ma ho bisogno di sapere che tipo di argomento passare alla chiamata. Alcune delle mie RequestTypeentità potrebbero prendere a LegoModel, alcune potrebbero prendere a PlasticModele altre potrebbero prendere a PeanutButterAndPeepsModel. Swift non è soddisfatto dell'ambiguità, quindi non ti permetterà di dichiarare una variabile di un protocollo che ha un tipo associato.

Allo stesso tempo ha perfettamente senso, ad esempio, creare un array di RequestTypequando SAPPIAMO che tutti usano l'estensione LegoModel. Sembra ragionevole, e lo è, ma hai bisogno di un modo per esprimerlo.

Un modo per farlo è creare una classe (o struct o enum) che associ un tipo reale al nome del tipo di modello astratto:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Ora è del tutto ragionevole dichiarare un array di LegoRequestTypeperché se li volessimo frobulatetutti sappiamo che dovremmo passare LegoModelogni volta a.

Questa sfumatura con i tipi associati rende speciale qualsiasi protocollo che li utilizzi. La Swift Standard Library ha protocolli come questo in particolare Collectiono Sequence.

Per consentire di creare un array di cose che implementano il Collectionprotocollo o un insieme di cose che implementano il protocollo di sequenza, la Standard Library impiega una tecnica chiamata "type-erasure" per creare i tipi di struct AnyCollection<T>o AnySequence<T>. La tecnica di cancellazione del tipo è piuttosto complessa da spiegare in una risposta di Stack Overflow, ma se cerchi sul Web ci sono molti articoli a riguardo.

Posso consigliare un video di Alex Gallagher sui protocolli con tipi associati (PAT) su YouTube.


40
"la tua soluzione è molto generica " 😂
Adolfo

6
Questa è una delle migliori spiegazioni che ho visto per questo problema
Keab42

1
Quindi BUONA spiegazione, risposta così singola.
Almas Adilbek

1
Cosa significa frobulate ?
Mofawaw

1
Questa risposta è stata un'ottima scusa per frobulare l'uso della parola frobulate.
ScottyBlades,

18

Da Swift 5.1 - Xcode 11

Puoi usare un tipo di risultato opaco per ottenere qualcosa del genere.

immagina questo:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Quindi quanto segue genera l'errore:

var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */

Ma rendere opaco il tipo aggiungendo la someparola chiave prima del tipo risolverà il problema e di solito è l'unica cosa che vogliamo:

var objectA: some ProtocolA = ClassA()

4

Un piccolo cambiamento nel design del tuo codice potrebbe renderlo possibile. Aggiungi un protocollo vuoto, non-associatedType, all'inizio della gerarchia del protocollo. Come questo...

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 altro esempio, con classi derivate dal protocollo RequestType, fare una coda e passare la coda a una funzione per stampare il tipo appropriato

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)

4

Swift 5.1

Un esempio di come è possibile utilizzare protocolli generici implementando un tipo e un protocollo di base associati :

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
    }
    
}

E un esempio di controller di visualizzazione:

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

    
}

0

Questo errore può verificarsi anche nel seguente scenario:

protocol MyProtocol {
    assosciatedtype SomeClass
    func myFunc() -> SomeClass
}

struct MyStuct {
    var myVar = MyProtocol
}

In questo caso, tutto ciò che devi fare per risolvere il problema è utilizzare i generici:

protocol MyProtocol {
    assosciatedtype SomeClass
    func myFunc() -> SomeClass
}

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.