I parametri generici configurabili da soli sono apparsi nei protocolli Swift con la parola chiave associatedtype fin dal primo rilascio di Swift, per dare un maggior controllo sulla tipizzazione durante l'astrazione del comportamento.
Se un protocollo dichiara un requisito in cui deve essere definito un certo tipo (ad esempio, il tipo di elementi in un contenitore) — senza associatedtype, utilizzare un protocollo con astrazione diventa impossibile: i tipi devono essere rigidamente fissati, si perde la possibilità di scrivere codice universale.
associatedtype consente di dichiarare in modo dichiarativo un tipo associato nel protocollo, che viene definito da una specifica implementazione del protocollo. Questo consente di scrivere protocolli e funzioni generiche senza conoscere i tipi specifici al momento della compilazione.
Esempio di un protocollo con 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 deduce che Item = Int }
Caratteristiche principali:
let x: MyContainer // errore),Perché è necessario associatedtype se ci sono già generics in Swift?
Risposta: Generics e associatedtype risolvono compiti simili, ma diversi. Il parametro generico si applica a un tipo o a una funzione, consentendo di creare strutture e metodi generici. Associatedtype, d'altra parte, è un'astrazione all'interno del protocollo, che richiede ai tipi implementati di determinare il proprio tipo. Utilizza generics per algoritmi universali o contenitori, e associatedtype per comportamenti tramite protocolli.
È possibile utilizzare un protocollo con associatedtype come tipo di variabile?
No, Swift non consente l'uso di tali protocolli come tipo direttamente, poiché le informazioni su associatedtype andrebbero perse. Per astrazione è possibile effettuare type erasure o utilizzare vincoli generici:
func printElements<C: MyContainer>(container: C) { for i in 0..<container.count { print(container[i]) } }
Come creare un protocollo con associatedtype "tipo" (type erasure)?
Per questo si utilizza il pattern di type erasure — si crea un involucro che memorizza l'implementazione interna tramite closure o box.
struct AnyContainer<T>: MyContainer { private let _append: (T) -> Void private let _count: () -> Int private let _subscript: (Int) -> T ... // inizializzazione tramite closure }
Uno sviluppatore ha creato numerosi protocolli con associatedtype per i modelli di dati e poi ha cercato di creare un array let boxes: [MyContainer], ma il compilatore ha restituito un errore. Alla fine, è stato necessario introdurre involucri aggiuntivi e perdere la sicurezza dei tipi.
Vantaggi:
Svantaggi:
Nel progetto, un protocollo con associatedtype è stato utilizzato per implementare collezioni, e per astrarre il lavoro con esse è stato creato un involucro di type erasure AnyCollection. Questo ha permesso di astrarsi dalla specifica implementazione delle collezioni nei livelli ViewModel, senza perdere la sicurezza dei tipi a livello di logica di business.
Vantaggi:
Svantaggi: