Załóżmy na chwilę, że dostosowujemy Twój protokół, aby dodać procedurę używającą skojarzonego typu:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
Swift miał pozwolić ci stworzyć tablicę RequestType
tak, jak chcesz. Mógłbym przekazać tablicę tych typów żądań do funkcji:
func handleQueueOfRequests(queue: [RequestType]) {
for request in queue {
request.frobulateModel()
}
}
Dochodzę do punktu, w którym chciałbym poruszyć wszystkie rzeczy, ale muszę wiedzieć, jaki rodzaj argumentu przekazać w rozmowie. Niektóre z moich RequestType
bytów mogą przyjąć LegoModel
, niektóre mogą przyjąć PlasticModel
, a inne mogą przyjąć PeanutButterAndPeepsModel
. Swift nie jest zadowolony z niejednoznaczności, więc nie pozwoli ci zadeklarować zmiennej protokołu, który ma powiązany typ.
Jednocześnie sensowne jest, na przykład, utworzenie tablicy, RequestType
kiedy WIEMY, że wszyscy używają rozszerzenia LegoModel
. Wydaje się to rozsądne i tak jest, ale potrzebujesz sposobu, aby to wyrazić.
Jednym ze sposobów jest utworzenie klasy (lub struktury lub wyliczenia), która kojarzy typ rzeczywisty z abstrakcyjną nazwą typu modelu:
class LegoRequestType: RequestType {
typealias Model = LegoModel
}
Teraz jest całkowicie rozsądne zadeklarowanie tablicy, LegoRequestType
ponieważ gdybyśmy chcieli ich frobulate
wszystkich, wiemy, że za LegoModel
każdym razem musielibyśmy podać a .
Ten niuans z typami powiązanymi sprawia, że każdy protokół, który ich używa, jest wyjątkowy. Biblioteka Swift Standard ma w szczególności takie protokoły Collection
lub Sequence
.
Aby umożliwić utworzenie tablicy elementów, które implementują Collection
protokół lub zestawu elementów, które implementują protokół sekwencji, biblioteka standardowa wykorzystuje technikę zwaną „typ-erasure” do tworzenia typów struktur AnyCollection<T>
lub AnySequence<T>
. Technika wymazywania typów jest dość złożona do wyjaśnienia w odpowiedzi na przepełnienie stosu, ale jeśli przeszukujesz Internet, jest wiele artykułów na jej temat.
Mogę polecić film Alexa Gallaghera na temat protokołów z powiązanymi typami (PAT) na YouTube.
Od wersji Swift 5.1 - Xcode 11
Możesz użyć nieprzezroczystego typu wyniku, aby osiągnąć coś takiego.
Wyobraź to sobie:
protocol ProtocolA { associatedtype number } class ClassA: ProtocolA { typealias number = Double }
Tak więc następujący błąd generuje:
var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */
Ale nieprzezroczystość typu przez dodanie
some
słowa kluczowego przed typem naprawi problem i zwykle jest to jedyna rzecz, której chcemy:var objectA: some ProtocolA = ClassA()
źródło
Swift 5.1
Przykładem , jak można wykorzystać protokoły generycznych poprzez wdrożenie powiązanych rodzajów i protokół bazowej :
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 } }
I przykładowy kontroler widoku:
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 }
źródło
Mała zmiana w projekcie twojego kodu może to umożliwić. Dodaj pusty, niepowiązany protokół typu na początku hierarchii protokołów. Lubię to...
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' }
Inny przykład, z klasami wywodzącymi się z protokołu RequestType, tworząc kolejkę i przekazując kolejkę do funkcji w celu wydrukowania odpowiedniego typu
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)
źródło
Ten błąd może również wystąpić w następującym scenariuszu:
protocol MyProtocol { assosciatedtype SomeClass func myFunc() -> SomeClass } struct MyStuct { var myVar = MyProtocol }
W takim przypadku wszystko, co musisz zrobić, aby rozwiązać problem, to użyć leków generycznych:
protocol MyProtocol { assosciatedtype SomeClass func myFunc() -> SomeClass } struct MyStuct<T: MyProtocol> { var myVar = T }
źródło