Generic (programmazione generica) — è un meccanismo di Swift che consente di scrivere tipi e funzioni con un tipo parametrizzato
Punti Chiave
Generic (tipo generico) — è una costruzione sintattica di Swift che consente di scrivere codice flessibile e riutilizzabile con parametrizzazione dei tipi. Invece di un tipo concreto, viene scritto un segnaposto tra parentesi angolari — di solito
Senza generics, si dovrebbero scrivere funzioni separate per ogni tipo: swapInts, swapStrings, swapDates. Una funzione generica swap sostituisce tutte e tre le varianti con una singola dichiarazione. Questo riduce la duplicazione, migliora la leggibilità e riduce la probabilità di errori durante la copia del codice.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
Il compilatore Swift genera codice specializzato per ogni utilizzo concreto di un generico. Ciò significa che la funzione generica swapValues
Funzioni generiche sono funzioni che accettano uno o più parametri di tipo. I parametri di tipo vengono specificati dopo il nome della funzione tra parentesi angolari:
Una funzione può avere multipli parametri generici. Ad esempio, la funzione findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
I vincoli K: Hashable e where V: Equatable garantiscono che findKey possa essere chiamata solo con un dizionario le cui chiavi siano hashable e i valori equatable. Tali vincoli non sono burocrazia ma una necessità: senza Hashable non è possibile indicizzare una ricerca per chiave, senza Equatable non è possibile confrontare value con dictValue.
where — è una parola chiave di Swift che impone vincoli aggiuntivi ai parametri generici. A differenza della semplice specifica di un protocollo tra parentesi angolari (
La clausola where viene scritta dopo la firma della funzione o del tipo, prima della parentesi graffa aperta. In where è possibile specificare che T: Comparable & Hashable (conforme a due protocolli simultaneamente), o che T.U == Int (concretizzare un associated type).
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
L’estensione con where aggiunge metodi solo per quei tipi che soddisfano la condizione. Container
Associated Types (tipi associati) — sono un modo per rendere un protocollo generico senza specificare un tipo concreto in fase di dichiarazione. Invece di
Usando where è possibile stabilire relazioni tra gli associated types di diversi protocolli. Ad esempio, si può richiedere che l’Item di un protocollo corrisponda a Iterator.Element di un altro. Ciò garantisce la compatibilità dei tipi a livello di compilatore.
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack concretizza Element come Int usando typealias. Il compilatore verifica che tutti i requisiti del protocollo siano soddisfatti con questo tipo. Senza associated types, si dovrebbe scrivere StackProtocol
Estensioni generiche consentono di aggiungere metodi a un tipo generico con vincoli aggiuntivi. Questo è il pattern di “conformità condizionale”, in cui un tipo ottiene funzionalità solo in determinate condizioni. Anche i subscript possono essere generici.
Un subscript può essere generico: accetta parametri generici tra parentesi angolari. Ciò è utile per l’accesso sicuro alle collezioni con indici arbitrari, dove l’indice può essere di diversi tipi.
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
Questa costruzione fornisce una potente composizione: il metodo sum appare solo sugli array numerici, e il subscript con indici arbitrari funziona con qualsiasi sequenza di indici interi. Il compilatore gestisce la visibilità di questi membri in base alle condizioni where.
Errori nell’uso dei generici sono spesso legati a vincoli errati o al tentativo di usare generici dove serve un tipo concreto. Esaminiamo tre scenari comuni affrontati dagli sviluppatori.
Aggiungere parametri generici dove un tipo concreto è sufficiente è un anti-pattern. Se una funzione lavora sempre con String, non è necessario renderla
Chiamare un metodo su un parametro generico senza specificare che il metodo esiste è un errore.
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
L’errore è chiaro: il generico
Domande Frequenti
Generic è un modo per scrivere codice che funziona con qualsiasi tipo, senza duplicare la stessa logica per Int, String e altri tipi. Invece di un tipo concreto, viene usato un segnaposto
Generic preserva le informazioni sul tipo concreto in fase di compilazione, consentendo al compilatore di verificare i tipi e generare codice ottimizzato. Any è un’eliminazione di tipo: qualsiasi valore può essere passato, ma il tipo viene perso ed è necessario un cast (as?).
Usa la sintassi T: SomeProtocol tra parentesi angolari o una clausola where separata where T: SomeProtocol. I vincoli garantiscono che il parametro generico supporti metodi e proprietà specifici.
Associated Type (associatedtype) è un segnaposto per un tipo all’interno di un protocollo. Il protocollo non specifica un tipo concreto ma dichiara un associatedtype, che viene concretizzato nell’implementazione: Array ha Element, Dictionary ha Key e Value.
Swift compila il codice generico in versioni specializzate per ogni tipo concreto. In pratica, una funzione generica per Int non è più lenta di una funzione scritta a mano per Int. Non c’è overhead di astrazione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche