Das Protokoll kann nur als generische Einschränkung verwendet werden, da es Self- oder AssociatedType-Anforderungen hat


92

Ich habe ein Protokoll RequestType und es hat zugeordnetes Typmodell wie unten.

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

}

Jetzt versuche ich, alle fehlgeschlagenen Anforderungen in eine Warteschlange zu stellen.

public class RequestEventuallyQueue {

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

}

Ich erhalte jedoch online den Fehler, let queue = [RequestType]()dass Protocol RequestType nur als generische Einschränkung verwendet werden kann, da es Self- oder AssociatedType-Anforderungen hat.

Antworten:


144

Angenommen, wir passen Ihr Protokoll für den Moment an, um eine Routine hinzuzufügen, die den zugehörigen Typ verwendet:

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

    func frobulateModel(aModel: Model)
}

Und Swift sollte es Ihnen ermöglichen, ein Array so zu erstellen, RequestTypewie Sie es möchten. Ich könnte ein Array dieser Anforderungstypen an eine Funktion übergeben:

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

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

Ich komme zu dem Punkt, an dem ich alle Dinge frobulieren möchte, aber ich muss wissen, welche Art von Argument in den Aufruf übergehen soll. Einige meiner RequestTypeEntitäten könnten ein nehmen LegoModel, einige könnten ein nehmen PlasticModel, und andere könnten ein nehmen PeanutButterAndPeepsModel. Swift ist mit der Mehrdeutigkeit nicht zufrieden, sodass Sie keine Variable eines Protokolls deklarieren können, dem ein Typ zugeordnet ist.

Gleichzeitig ist es durchaus sinnvoll, beispielsweise ein Array zu erstellen, RequestTypewenn wir wissen, dass alle das verwenden LegoModel. Dies scheint vernünftig und ist es auch, aber Sie brauchen eine Möglichkeit, dies auszudrücken.

Eine Möglichkeit, dies zu tun, besteht darin, eine Klasse (oder Struktur oder Aufzählung) zu erstellen, die dem Namen des abstrakten Modelltyps einen realen Typ zuordnet:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

Jetzt ist es völlig vernünftig, eine Reihe von zu deklarieren, LegoRequestTypedenn wenn wir frobulatealle wollten, wissen wir, dass wir LegoModeljedes Mal eine übergeben müssen.

Diese Nuance mit zugeordneten Typen macht jedes Protokoll, das sie verwendet, zu etwas Besonderem. Die Swift Standard Library verfügt über solche Protokolle, insbesondere Collectionoder Sequence.

Damit Sie ein Array von Dingen erstellen können, die das CollectionProtokoll implementieren , oder eine Reihe von Dingen, die das Sequenzprotokoll implementieren, verwendet die Standardbibliothek eine Technik namens "Typlöschung", um die Strukturtypen AnyCollection<T>oder zu erstellen AnySequence<T>. Die Typ-Lösch-Technik ist in einer Stapelüberlauf-Antwort ziemlich komplex zu erklären, aber wenn Sie im Web suchen, gibt es viele Artikel darüber.

Ich kann ein Video von Alex Gallagher über Protokolle mit zugehörigen Typen (PATs) auf YouTube empfehlen .


35
"Ihre Lösung ist sehr allgemein " 😂
Adolfo

6
Dies ist eine der besten Erklärungen, die ich für dieses Problem gesehen habe
Keab42

1
Also gute Erklärung, so eine einzige Antwort.
Almas Adilbek

1
Was bedeutet Frobulat ?
Mofawaw

In den 1980er Jahren gab es eine Text-Adventure-Spieleserie, die mit dem Spiel Zork begann. In dieser Spieleserie gab es die Frobozz Magic Company. Sie pflegten Dinge zu frobulieren. Kurz gesagt, es ist eine dumme Phrase für eine unspezifische Handlung.
Scott Thompson

12

Ab Swift 5.1

Sie können einen undurchsichtigen Ergebnistyp verwenden, um so etwas zu erreichen.

Stell dir das vor:

protocol ProtocolA {
    associatedtype number
}

class ClassA: ProtocolA {
    typealias number = Double
}

Folgendes erzeugt also den Fehler:

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

Wenn Sie den Typ jedoch durch Hinzufügen des Schlüsselworts vor dem Typ undurchsichtig machen,some wird das Problem behoben. In der Regel ist dies das einzige, was wir möchten:

var objectA: some ProtocolA = ClassA()

somescheint als iOS 13+
Cruceo

3

Eine kleine Änderung im Design Ihres Codes könnte dies ermöglichen. Fügen Sie oben in Ihrer Protokollhierarchie ein leeres, nicht zugeordnetes Typprotokoll hinzu. So was...

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'

}

Ein weiteres Beispiel mit Klassen, die vom Protokoll RequestType abgeleitet sind, eine Warteschlange erstellen und die Warteschlange an eine Funktion übergeben, um den entsprechenden Typ zu drucken

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)

3

Swift 5.1

Ein Beispiel , wie Sie verwenden können , generische Protokolle durch die Implementierung eine zugehörige Art und Basisprotokolls :

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

Und ein Beispiel für View Controller:

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

    
}

-1

Dieser Fehler kann auch im folgenden Szenario auftreten:

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

struct MyStuct {
    var myVar = MyProtocol
}

In diesem Fall müssen Sie lediglich Generika verwenden, um das Problem zu beheben:

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

struct MyStuct<T: MyProtocol> {
    var myVar = T
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.