Zelf in te stellen generieke parameters zijn in Swift protocollen geïntroduceerd met het sleutelwoord associatedtype sinds de eerste release van Swift, om meer controle over typevorming te geven bij het abstraheren van gedrag.
Als een protocol een vereiste definieert waarin een bepaald type moet worden gedefinieerd (bijv. type elementen in een container) — zonder associatedtype is het onmogelijk om een protocol met abstractie te gebruiken: types moeten strikt worden vastgelegd, wat de mogelijkheid om generieke code te schrijven verliest.
associatedtype maakt het mogelijk om declaratief in een protocol een gerelateerd type te definiëren dat door een specifieke implementatie van het protocol wordt bepaald. Dit laat toe protocollen en generieke functies te schrijven zonder de specifieke types te kennen op het moment van compileren.
Voorbeeld van een protocol met associatedtype:
protocol MyContainer { associatedtype Item var count: Int { get } mutating func append(_ item: Item) subscript(i: Int) -> Item { get } } struct IntStack: MyContainer { var items = [Int]() mutating func append(_ item: Int) { items.append(item) } var count: Int { items.count } subscript(i: Int) -> Int { items[i] } // Swift concludeert automatisch dat Item = Int }
Belangrijke kenmerken:
let x: MyContainer // fout),Waarom is associatedtype nodig als er al generics in Swift zijn?
Antwoord: Generics en associatedtype lossen vergelijkbare, maar verschillende problemen op. Een generieke parameter wordt toegepast op een type of functie, waardoor het mogelijk is om generieke structuren en methoden te creëren. Associatedtype daarentegen is een abstractie binnen een protocol die vereist dat implementerende types hun eigen type definiëren. Gebruik generics voor universële algoritmen of containers, en associatedtype voor gedrag via protocollen.
Kan een protocol met associatedtype worden gebruikt als type variabele?
Nee, Swift laat niet toe dat zulke protocollen direct als type worden gebruikt, omdat de informatie over associatedtype verloren gaat. Voor abstractie kun je type erasure doen of gebruik maken van generieke beperkingen:
func printElements<C: MyContainer>(container: C) { for i in 0..<container.count { print(container[i]) } }
Hoe maak je een protocol met associatedtype "type" (type erasure)?
Hiervoor gebruiken ze het type erasure patroon — ze creëren een wrapper die de interne implementatie opslaat via closure of box.
struct AnyContainer<T>: MyContainer { private let _append: (T) -> Void private let _count: () -> Int private let _subscript: (Int) -> T ... // initialisatie via closures }
Een ontwikkelaar creëerde meerdere protocollen met associatedtype voor datamodellen, en probeerde toen een array let boxes: [MyContainer] te maken, wat resulteerde in een fout van de compiler. Uiteindelijk moest hij onnodige wrappers invoeren en typeveiligheid verliezen.
Voordelen:
Nadelen:
In het project werd een protocol met associatedtype gebruikt voor de implementatie van collecties, en voor de abstractie van hun werking werd een aparte type erasure wrapper AnyCollection gemaakt. Dit maakte het mogelijk om zich te abstraheren van de specifieke implementatie van collecties in de ViewModel-laag, zonder typeveiligheid op het niveau van de business logic laag te verliezen.
Voordelen:
Nadelen: