Storia della questione: Type erasure (cancellazione del tipo) è un pattern che è emerso in Swift come risposta alle limitazioni dei protocolli con associated type o requisiti di Self. Un protocollo non può essere utilizzato come tipo direttamente senza type erasure se contiene tipi associati, poiché il compilatore non conosce l'implementazione concreta. Questo si verifica spesso quando si creano contenitori di dati, collezioni, flussi, dove è importante l'astrazione.
Problema: Se c'è un protocollo con associatedtype, ad esempio:
protocol Animal { associatedtype Food func eat(_ food: Food) }
non è possibile dichiarare una variabile di tipo Animal. Abbiamo bisogno di un "tipo che cancella", che consente di accedere a diversi tipi concreti attraverso l'astrazione:
let zoo: [any Animal] // Errore di compilazione
Soluzione: Type erasure si realizza tramite wrapper (ad esempio, il pattern Box o struct AnyXxx), fornendo un'interfaccia unica, nascondendo i dettagli del tipo reale:
struct AnyAnimal<F>: Animal { private let _eat: (F) -> Void init<A: Animal>(_ base: A) where A.Food == F { _eat = base.eat } func eat(_ food: F) { _eat(food) } }
Ora è possibile memorizzare diverse implementazioni di Animal con lo stesso Food:
let animals: [AnyAnimal<Grass>] = [AnyAnimal(Cow()), AnyAnimal(Sheep())]
Caratteristiche chiave:
È possibile utilizzare un protocollo con associatedtype senza type erasure come tipo di proprietà o array?
No, non è possibile. Il compilatore richiede la specificazione di tutti i tipi associati. Ad esempio, la seguente dichiarazione genererà un errore:
let array: [Animal] // Errore: 'Animal' può essere utilizzato solo come vincolo generico
Può type erasure sostituire l'ereditarietà?
No, type erasure non è una sostituzione completa dell'ereditarietà. Risolve il problema dell'astrazione per i protocolli con associatedtype, mentre l'ereditarietà è utilizzata per l'implementazione di logiche comuni e il riutilizzo del codice tra classi.
È necessario implementare tutti i metodi e le proprietà del protocollo all'interno del wrapper di type erasure?
Sì, è obbligatorio. Type erasure funziona solo se il wrapper replica completamente l'interfaccia esterna del protocollo, altrimenti parte delle funzionalità non sarà disponibile.
In un progetto reale, tutto il lavoro con le fonti di dati è costruito attraverso type erasure, anche quando non è necessaria la presenza di associatedtype. Il codice diventa difficile da mantenere, i nuovi sviluppatori sono confusi.
Pro:
Contro:
Type erasure è applicato solo per l'astrazione della fonte di dati, che incapsula diverse strategie di caricamento (rete, locali), ognuna con i propri tipi. Nel resto, il codice è trasparente.
Pro:
Contro: