KeyPath è un percorso tipizzato verso una proprietà di un oggetto in Swift, rappresentato come un valore di prima classe. A differenza delle chiavi stringa in KVC (Key-Value Coding), KeyPath viene verificato dal compilatore in fase di compilazione: il compilatore conosce il tipo dell’oggetto radice, il tipo della proprietà e può garantire che il percorso esista. KeyPath viene utilizzato in Combine per il binding reattivo, in SwiftUI per il binding bidirezionale e nella libreria standard per ordinare e filtrare le collezioni. Secondo Apple Developer, 2025, KeyPath è un elemento fondamentale per la programmazione funzionale in Swift.
Punti chiave
KeyPath è un tipo della libreria standard di Swift che rappresenta un percorso verso una proprietà di un oggetto. A differenza dell’accesso manuale a una proprietà con notazione punto (object.property) o una chiave stringa (value(forKey:)), KeyPath è un oggetto di prima classe: può essere passato come argomento, memorizzato in una variabile e composto.
Un KeyPath viene scritto con una barra rovesciata seguita dal percorso verso la proprietà: \Person.name. Il percorso può essere annidato: \Person.address.city. Il compilatore verifica che Person abbia una proprietà name e che name sia effettivamente un String. Se la proprietà non esiste o il tipo è sbagliato, il codice non compila.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (sola lettura)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
L’accesso a una proprietà tramite KeyPath avviene attraverso il subscript object[keyPath: path]. Per le proprietà di sola lettura, viene usato KeyPath. Se la proprietà è mutabile (var), WritableKeyPath è disponibile. Per i tipi riferimento, ReferenceWritableKeyPath consente di modificare la proprietà direttamente tramite KeyPath.
KeyPath ha una gerarchia di sottotipi che riflette il livello di accesso alla proprietà. La gerarchia di base è: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Ogni sottotipo aggiunge capacità.
KeyPath — sola lettura. WritableKeyPath — lettura e scrittura per tipi valore (struct). ReferenceWritableKeyPath — lettura e scrittura per tipi riferimento (class). Swift seleziona automaticamente il sottotipo corretto in base al contesto.
| Tipo | Accesso | Tipo oggetto | Mutazione |
|---|---|---|---|
| KeyPath | Sola lettura | Qualsiasi | No |
| WritableKeyPath | Lettura e scrittura | Tipo valore (inout) | Tramite contesto mutating |
| ReferenceWritableKeyPath | Lettura e scrittura | Tipo riferimento | Assegnazione diretta |
// WritableKeyPath per tipo valore
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath per classe
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
Una differenza importante: per i tipi valore, l’impostazione tramite KeyPath richiede var (contesto inout), mentre per i tipi riferimento è sufficiente una proprietà mutabile. Ciò è in linea con la semantica generale di Swift: i tipi valore vengono passati per valore, quindi la mutazione richiede la mutabilità del contenitore.
KeyPath gioca un ruolo centrale in Combine e SwiftUI. In Combine, l’operatore assign utilizza KeyPath per legare il valore di un publisher a una proprietà di un oggetto. In SwiftUI, Binding usa KeyPath per il collegamento bidirezionale tra modello e vista.
L’operatore assign(to:on:) accetta un ReferenceWritableKeyPath e un oggetto. Quando il publisher emette un valore, viene automaticamente scritto nella proprietà specificata. Questo è un modo dichiarativo per gestire lo stato senza assegnazioni manuali.
import Combine
class SettingsViewModel: ObservableObject {
@Published var volume: Float = 0.5
var cancellables = Set<AnyCancellable>()
func bindSlider(publisher: AnyPublisher<Float, Never>) {
publisher
.assign(to: \SettingsViewModel.volume, on: self)
.store(in: &cancellables)
}
}
In SwiftUI, @Binding usa KeyPath per connettere una vista padre e figlia. Il padre passa un Binding
KeyPath consente di scrivere funzioni di ordinamento e filtraggio flessibili e type-safe. Invece di passare una closure ogni volta, puoi passare un KeyPath alla proprietà in base a cui vuoi ordinare la collezione. Questo rende il codice più pulito e riduce la duplicazione.
La funzione sorted
extension Sequence {
func sorted<Value: Comparable>(
by keyPath: KeyPath<Element, Value>,
ascending: Bool = true
) -> [Element] {
ascending
? self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
: self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] }
}
}
let sortedByName = people.sorted(by: \Person.name)
let sortedByAgeDesc = people.sorted(by: \Person.age, ascending: false)
Questa implementazione ordina per qualsiasi proprietà Comparable senza scrivere closure. Per ordinare per cognome, basta cambiare KeyPath: \Person.lastName. Se è necessario ordinare per una chiave personalizzata (ad esempio, lunghezza del nome), viene usato map internamente — lo sviluppatore può comunque scrivere la propria closure per casi non standard.
AnyKeyPath è il tipo base nella gerarchia KeyPath che cancella le informazioni sui tipi specifici di radice e valore. AnyKeyPath non sa su quale tipo di oggetto viene chiamato né quale tipo di valore restituisce. Questo è utile per memorizzare KeyPaths eterogenei in collezioni e per la riflessione.
AnyKeyPath viene utilizzato quando è necessario memorizzare un elenco di percorsi verso diverse proprietà di diversi tipi. Ad esempio, in un configuratore di UI, dove ogni percorso configura una proprietà specifica e i tipi di proprietà possono differire. AnyKeyPath consente di lavorare uniformemente con tutti i percorsi.
protocol Configurable {
func apply(_ keyPath: any PartialKeyPath<Self>, value: any)
}
extension Configurable {
func configure(_ pairs: (any KeyPath<Self, any>, any)...) {
for (path, value) in pairs {
apply(path, value: value)
}
}
}
extension Person: Configurable { }
// Consente di configurare proprietà tramite AnyKeyPath
AnyKeyPath richiede cast di tipo durante il recupero del valore, poiché il tipo specifico è stato cancellato. Per la massima sicurezza dei tipi, un KeyPath
KeyPath è significativamente più efficiente delle chiavi stringa KVC (value(forKey:)). Le chiavi stringa vengono risolte attraverso il meccanismo runtime di Objective-C, inclusa l’analisi della stringa, la ricerca in runtime e il cast di tipo. KeyPath è un meccanismo statico basato su strutture native Swift.
Secondo i benchmark, KeyPath è in media 10-20 volte più veloce di value(forKey:) per l’accesso alle proprietà. La differenza è dovuta all’assenza di ricerca in runtime e dispatch dinamico. KeyPath accede direttamente all’offset della proprietà in memoria con una sufficiente ottimizzazione del compilatore.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC non è solo più lento, ma anche non type-safe: la stringa “name” potrebbe non esistere e il cast as! String potrebbe causare un crash. KeyPath garantisce l’esistenza della proprietà e la correttezza del tipo in fase di compilazione. Per le sezioni critiche per le prestazioni (animazioni, elenchi con migliaia di elementi), KeyPath è l’unica scelta corretta.
Domande frequenti
KeyPath è un puntatore a una proprietà di un oggetto che può essere passato come un valore normale. Invece di scrivere person.name, crei un percorso \Person.name e lo usi per leggere o scrivere la proprietà su qualsiasi istanza di Person.
KeyPath — sola lettura. WritableKeyPath — per leggere e scrivere tipi valore (struct) tramite contesto inout. ReferenceWritableKeyPath — per leggere e scrivere tipi riferimento (class) senza condizioni aggiuntive.
KeyPath è ampiamente utilizzato in Combine (assign(to:on:)), SwiftUI (Binding, FocusState), ordinamento di collezioni (sorted(by:)), Core Data (NSSortDescriptor) e per l’accesso sicuro a proprietà annidate.
Sì, KeyPath è completamente type-safe. Un errore nel percorso (refuso, tipo sbagliato) viene rilevato in fase di compilazione, non in fase di esecuzione. value(forKey:) usa stringhe e può crashare con NSUndefinedKeyException se la chiave non esiste.
Non direttamente — Swift non supporta la creazione dinamica di KeyPath da una stringa. Tuttavia, puoi usare AnyKeyPath per memorizzare e passare KeyPath ottenuti staticamente. Per l’accesso dinamico alle proprietà tramite stringa, usa value(forKey:) con il runtime Objective-C.
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