Protocollen met associatedtype en where-beperkingen zijn een krachtige mechanismen voor type-abstrahering in Swift. Dit wordt vaak gebruikt voor het implementeren van generieke protocollen, waarbij het specifieke type wordt bepaald door de implementatie. Historisch gezien konden protocollen met associatedtype in eerdere versies van Swift niet worden gebruikt als specifieke types (existentiële types), wat beperkingen met zich meebracht voor het gebruik van dergelijke protocollen in collecties en interfaces. Later heeft Swift mechanismen toegevoegd voor het beperken van associatedtype met behulp van where-voorwaarden, wat het mogelijk maakt om flexibele en veilige abstracties te creëren voor complexe scenario's.
Probleem: Vaak ontstaat de behoefte om een protocol te creëren dat abstraheert van specifieke collecties of gegevensbehandelaars. Echter, standaardprotocollen kunnen onvoldoende flexibel zijn: niet alle types kunnen worden gegeneraliseerd zonder kennis van de gerelateerde types, en de logica van het overerven van protocollen met associatedtype is niet altijd duidelijk.
Oplossing: gebruik waar-beperkingen om de vereisten voor implementaties te verduidelijken, waardoor protocollen met adaptief gedrag kunnen worden gemaakt.
Voorbeeldcode:
protocol Storage { associatedtype Element func add(_ item: Element) } // Beperkt protocol voor het opslaan van alleen getallen protocol NumericStorage: Storage where Element: Numeric { func sum() -> Element } struct IntStorage: NumericStorage { private var items: [Int] = [] func add(_ item: Int) { items.append(item) } func sum() -> Int { items.reduce(0, +) } }
Belangrijkste kenmerken:
Kan een protocol met associatedtype als type worden gebruikt (bijvoorbeeld voor een collectie)?
Nee, niet direct. Een protocol met associatedtype is een PAT (protocol with associated type) en kan niet worden gebruikt als een existentiëel type. Het is niet mogelijk om bijvoorbeeld een array [Storage] te declareren, dit kan alleen met behulp van type-erasure.
Hoe type-erasure voor een protocol met associatedtype te implementeren?
Via een hulwrap, die het echte type-implementatie verbergt.
struct AnyStorage<T>: Storage { private let _add: (T) -> Void init<S: Storage>(_ storage: S) where S.Element == T { _add = storage.add } func add(_ item: T) { _add(item) } }
Wat is het verschil tussen associatedtype en een generieke parameter van een protocol?
associatedtype definieert een specifiek type dat moet worden geïmplementeerd overeenkomstig, terwijl een generieke parameter expliciet in het protocol of de functie wordt vermeld, maar niet is toegestaan in de declaratie van het protocol. Een protocol kan niet generiek zijn volgens de syntaxis, alleen via associatedtype.
Een ontwikkelaar probeerde [Storage] te gebruiken voor het opslaan van willekeurige collecties. De code compileert niet, wat leidt tot impliceerde casts of het gebruik van Any/unsafe benaderingen.
Voordelen:
Nadelen:
Een ontwikkelaar implementeerde AnyStorage<T> om de specifieke implementatie te verbergen, voegde waar-beperkingen toe om ervoor te zorgen dat alleen de juiste types correct werken.
Voordelen:
Nadelen: