KeyPath — este o cale tipizată către o proprietate a unui obiect în Swift, reprezentată ca o valoare de primă clasă. Spre deosebire de cheile text în KVC (Key-Value Coding), KeyPath este verificat de compilator în faza de compilare: compilatorul știe tipul obiectului rădăcină, tipul proprietății și poate garanta că această cale există. KeyPath este utilizat în Combine pentru legarea reactivă, în SwiftUI pentru legarea bidirecțională și în biblioteca standard pentru sortarea și filtrarea colecțiilor. Potrivit Apple Developer, 2025, KeyPath este un bloc fundamental de construcție pentru programarea funcțională în Swift.
Principalele puncte
KeyPath — este un tip din biblioteca standard Swift care reprezintă o cale către o proprietate a unui obiect. Spre deosebire de accesul manual la proprietate prin punct (object.property) sau o cheie text (value(forKey:)), KeyPath este un obiect de primă clasă: poate fi transmis ca argument, stocat într-o variabilă și combinat.
KeyPath se scrie printr-un backslash și calea către proprietate: \Person.name. Calea poate fi imbricată: \Person.address.city. Compilatorul verifică că Person are proprietatea name și că name este într-adevăr de tip String. Dacă proprietatea nu există sau tipul este incorect, codul nu se compilează.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (doar citire)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
Accesul la proprietate prin KeyPath se realizează prin subscriptul object[keyPath: path]. Pentru proprietăți ReadOnly se utilizează KeyPath. Dacă proprietatea este mutabilă (var), este disponibil WritableKeyPath. Pentru tipuri de referință — ReferenceWritableKeyPath, permițând modificarea proprietății prin KeyPath însuși.
KeyPath are o ierarhie de subtipuri care reflectă nivelul de acces la proprietate. Ierarhia de bază: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Fiecare subtip adaugă capacități.
KeyPath — doar citire. WritableKeyPath — citire și scriere pentru tipuri valoare (struct). ReferenceWritableKeyPath — citire și scriere pentru tipuri de referință (class). Swift alege automat subtipul potrivit în funcție de context.
| Tip | Acces | Tipul obiectului | Modificare |
|---|---|---|---|
| KeyPath | ReadOnly | Oricare | Nu |
| WritableKeyPath | ReadWrite | Tip valoare (inout) | Prin context mutating |
| ReferenceWritableKeyPath | ReadWrite | Tip referință | Atribuire directă |
// WritableKeyPath pentru tipul valoare
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath pentru clasă
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
Diferența importantă: pentru tipurile valoare, setarea prin KeyPath necesită var (context inout), iar pentru tipurile de referință — o proprietate mutabilă. Aceasta corespunde semanticii generale Swift: tipurile valoare sunt transmise prin valoare, deci modificarea necesită mutabilitatea containerului.
KeyPath joacă un rol central în Combine și SwiftUI. În Combine, operatorul assign folosește KeyPath pentru a lega valoarea editorului de o proprietate a obiectului. În SwiftUI, Binding folosește KeyPath pentru legarea bidirecțională a modelului și vizualizării.
Operatorul assign(to:on:) primește un ReferenceWritableKeyPath și un obiect. Când editorul trimite o valoare, aceasta este scrisă automat în proprietatea specificată. Aceasta este o modalitate declarativă de gestionare a stării fără atribuiri manuale.
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)
}
}
În SwiftUI, @Binding folosește KeyPath pentru a conecta View-ul părinte și copil. Părinte transmite un Binding
KeyPath permite scrierea de funcții flexibile și tip-sigure de sortare și filtrare. În loc să transmiteți o închidere de fiecare dată, puteți transmite un KeyPath către proprietatea după care doriți să sortați colecția. Acest lucru face codul mai curat și reduce duplicarea.
Funcția 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)
O astfel de implementare sortează după orice proprietate Comparable fără a scrie închideri. Pentru sortarea după nume de familie este suficient să schimbați KeyPath: \Person.lastName. Dacă trebuie să sortați după o cheie personalizată (de exemplu, lungimea numelui), se folosește map în interior — dezvoltatorul poate scrie în continuare propria închidere pentru cazuri nestandard.
AnyKeyPath — este tipul de bază în ierarhia KeyPath care șterge informația despre tipurile concrete ale rădăcinii și valorii. AnyKeyPath nu știe pe ce tip de obiect este apelat și ce tip de valoare returnează. Acest lucru este util pentru stocarea KeyPath-urilor eterogene în colecții și pentru reflecție.
AnyKeyPath este utilizat când trebuie să stocați o listă de căi către diferite proprietăți de diferite tipuri. De exemplu, într-un configurator UI, unde fiecare cale configurează o anumită proprietate, iar tipurile proprietăților pot diferi. AnyKeyPath permite lucrul uniform cu toate căile.
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 { }
// Permite configurarea proprietăților prin AnyKeyPath
AnyKeyPath necesită conversie de tip la extragerea valorii, deoarece tipul concret este șters. Pentru siguranță completă a tipurilor, este întotdeauna preferat KeyPath
KeyPath este semnificativ mai performant decât cheile text KVC (value(forKey:)). Cheile text sunt calculate prin mecanismul runtime Objective-C, incluzând parsarea textului, căutarea în runtime și conversia tipurilor. KeyPath este un mecanism static bazat pe structurile native Swift.
Conform testelor, KeyPath este în medie de 10-20 de ori mai rapid decât value(forKey:) pentru accesul la proprietăți. Diferența se datorează absenței căutării în runtime și a distribuției dinamice. KeyPath accesează direct offset-ul proprietății în memorie cu o optimizare suficientă a compilatorului.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC nu este doar mai lent, ci și nesigur din punct de vedere al tipurilor: textul „name“ poate să nu existe, iar conversia as! String poate eșua. KeyPath garantează existența proprietății și corectitudinea tipului în faza de compilare. Pentru zonele critice din punct de vedere al performanței (animații, liste cu mii de elemente), KeyPath este singura alegere corectă.
Întrebări frecvente
KeyPath — este un pointer către o proprietate a unui obiect care poate fi transmis ca o valoare obișnuită. În loc să scrieți person.name, creați calea \Person.name și o folosiți pentru a citi sau scrie proprietatea în orice instanță Person.
KeyPath — doar citire. WritableKeyPath — pentru citirea și scrierea tipurilor valoare (struct) prin context inout. ReferenceWritableKeyPath — pentru citirea și scrierea tipurilor de referință (class) fără condiții suplimentare.
KeyPath este utilizat pe scară largă în Combine (assign(to:on:)), SwiftUI (Binding, FocusState), sortarea colecțiilor (sorted(by:)), Core Data (NSSortDescriptor) și pentru acces sigur la proprietăți imbricate.
Da, KeyPath este complet sigur din punct de vedere al tipurilor. O eroare în cale (greșeală de tipar, tip incorect) este detectată în faza de compilare, nu în runtime. value(forKey:) folosește texte și poate eșua cu NSUndefinedKeyException dacă cheia nu există.
Direct — nu, Swift nu suportă crearea dinamică a KeyPath dintr-un text. Cu toate acestea, puteți folosi AnyKeyPath pentru a stoca și transmite KeyPath-uri obținute static. Pentru acces dinamic la proprietăți prin text, utilizați value(forKey:) cu runtime Objective-C.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și