KeyPath — je typovaná cesta k vlastnosti objektu ve Swiftu, reprezentovaná jako hodnota první třídy. Na rozdíl od textových klíčů v KVC (Key-Value Coding), KeyPath je kontrolován kompilátorem ve fázi kompilace: kompilátor zná typ kořenového objektu, typ vlastnosti a může zaručit, že cesta existuje. KeyPath se používá v Combine pro reaktivní vázání, ve SwiftUI pro obousměrné bindování a ve standardní knihovně pro řazení a filtrování kolekcí. Podle Apple Developer, 2025 je KeyPath základním stavebním blokem pro funkcionální programování ve Swiftu.
Hlavní body
KeyPath — je typ ze standardní knihovny Swift, který představuje cestu k vlastnosti objektu. Na rozdíl od ručního přístupu k vlastnosti přes tečku (object.property) nebo textového klíče (value(forKey:)), KeyPath je objekt první třídy: lze jej předat jako argument, uložit do proměnné a kombinovat.
KeyPath se zapisuje pomocí zpětného lomítka a cesty k vlastnosti: \Person.name. Cesta může být vnořená: \Person.address.city. Kompilátor kontroluje, že Person má vlastnost name a že name je skutečně typu String. Pokud vlastnost neexistuje nebo je typ nesprávný, kód se nezkompiluje.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (pouze pro čtení)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
Přístup k vlastnosti přes KeyPath se provádí pomocí subscriptu object[keyPath: path]. Pro vlastnosti ReadOnly se používá KeyPath. Pokud je vlastnost měnitelná (var), je k dispozici WritableKeyPath. Pro referenční typy — ReferenceWritableKeyPath, umožňující změnu vlastnosti přes samotný KeyPath.
KeyPath má hierarchii podtypů odrážející úroveň přístupu k vlastnosti. Základní hierarchie: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Každý podtyp přidává možnosti.
KeyPath — pouze pro čtení. WritableKeyPath — čtení a zápis pro hodnotové typy (struct). ReferenceWritableKeyPath — čtení a zápis pro referenční typy (class). Swift automaticky vybírá správný podtyp na základě kontextu.
| Typ | Přístup | Typ objektu | Změna |
|---|---|---|---|
| KeyPath | ReadOnly | Libovolný | Ne |
| WritableKeyPath | ReadWrite | Hodnotový typ (inout) | Přes mutating kontext |
| ReferenceWritableKeyPath | ReadWrite | Referenční typ | Přímé přiřazení |
// WritableKeyPath pro hodnotový typ
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath pro třídu
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
Důležitý rozdíl: pro hodnotové typy vyžaduje nastavení přes KeyPath var (kontext inout), zatímco pro referenční typy — měnitelnou vlastnost. To odpovídá obecné sémantice Swiftu: hodnotové typy se předávají hodnotou, takže změna vyžaduje mutovatelnost kontejneru.
KeyPath hraje centrální roli v Combine a SwiftUI. V Combine operátor assign používá KeyPath k vázání hodnoty vydavatele k vlastnosti objektu. Ve SwiftUI Binding používá KeyPath k obousměrnému propojení modelu a zobrazení.
Operátor assign(to:on:) přijímá ReferenceWritableKeyPath a objekt. Když vydavatel odešle hodnotu, je automaticky zapsána do zadané vlastnosti. Toto je deklarativní způsob správy stavu bez ručních přiřazení.
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)
}
}
Ve SwiftUI @Binding používá KeyPath k propojení nadřazeného a podřízeného View. Rodič předává Binding
KeyPath umožňuje psát flexibilní a typově bezpečné funkce řazení a filtrování. Místo předávání uzávěru pokaždé můžete předat KeyPath k vlastnosti, podle které chcete kolekci seřadit. To činí kód čistším a snižuje duplicitu.
Funkce 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)
Taková implementace řadí podle libovolné Comparable vlastnosti bez psaní uzávěrů. Pro řazení podle příjmení stačí změnit KeyPath: \Person.lastName. Pokud je třeba řadit podle vlastního klíče (např. délka jména), používá se map uvnitř — vývojář může stále napsat vlastní uzávěr pro nestandardní případy.
AnyKeyPath — je základní typ v hierarchii KeyPath, který maže informace o konkrétních typech kořene a hodnoty. AnyKeyPath neví, na jakém typu objektu je volán a jaký typ hodnoty vrací. To je užitečné pro ukládání heterogenních KeyPath v kolekcích a pro reflexi.
AnyKeyPath se používá, když potřebujete uložit seznam cest k různým vlastnostem různých typů. Například v konfigurátoru UI, kde každá cesta konfiguruje určitou vlastnost a typy vlastností se mohou lišit. AnyKeyPath umožňuje jednotnou práci se všemi cestami.
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 { }
// Umožňuje konfigurovat vlastnosti přes AnyKeyPath
AnyKeyPath vyžaduje konverzi typů při získávání hodnoty, protože konkrétní typ je smazán. Pro úplnou typovou bezpečnost je vždy preferován typovaný KeyPath
KeyPath je výrazně výkonnější než textové klíče KVC (value(forKey:)). Textové klíče jsou vypočítávány pomocí runtime mechanismu Objective-C, včetně parsování textu, vyhledávání v runtime a konverze typů. KeyPath je statický mechanismus založený na nativních strukturách Swift.
Podle testů je KeyPath v průměru 10-20krát rychlejší než value(forKey:) pro přístup k vlastnostem. Rozdíl je způsoben absencí vyhledávání v runtime a dynamického dispatchování. KeyPath přímo přistupuje k offsetu vlastnosti v paměti při dostatečné optimalizaci kompilátoru.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC je nejen pomalejší, ale také nebezpečný z hlediska typů: text „name“ nemusí existovat a konverze as! String může selhat. KeyPath zaručuje existenci vlastnosti a správnost typu ve fázi kompilace. Pro kritická místa z hlediska výkonu (animace, seznamy tisíců prvků) je KeyPath jedinou správnou volbou.
Často kladené otázky
KeyPath — je ukazatel na vlastnost objektu, který lze předat jako běžnou hodnotu. Místo psaní person.name vytvoříte cestu \Person.name a použijete ji ke čtení nebo zápisu vlastnosti v libovolné instanci Person.
KeyPath — pouze pro čtení. WritableKeyPath — pro čtení a zápis hodnotových typů (struct) pomocí inout kontextu. ReferenceWritableKeyPath — pro čtení a zápis referenčních typů (class) bez dalších podmínek.
KeyPath je široce používán v Combine (assign(to:on:)), SwiftUI (Binding, FocusState), řazení kolekcí (sorted(by:)), Core Data (NSSortDescriptor) a pro bezpečný přístup k vnořeným vlastnostem.
Ano, KeyPath je plně typově bezpečný. Chyba v cestě (překlep, nesprávný typ) je detekována ve fázi kompilace, nikoli za běhu. value(forKey:) používá texty a může selhat s NSUndefinedKeyException, pokud klíč neexistuje.
Přímo — ne, Swift nepodporuje dynamické vytváření KeyPath z textu. Můžete však použít AnyKeyPath pro ukládání a předávání staticky získaných KeyPath. Pro dynamický přístup k vlastnostem pomocí textu použijte value(forKey:) s Objective-C runtime.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také