KeyPath é um caminho tipado para uma propriedade de um objeto em Swift, representado como um valor de primeira classe. Ao contrário das chaves de string em KVC (Key-Value Coding), KeyPath é verificado pelo compilador em tempo de compilação: o compilador conhece o tipo do objeto raiz, o tipo da propriedade e pode garantir que o caminho existe. KeyPath é usado no Combine para ligação reativa, no SwiftUI para ligação bidirecional e na biblioteca padrão para ordenar e filtrar coleções. De acordo com Apple Developer, 2025, KeyPath é um bloco fundamental para programação funcional em Swift.
Principais pontos
KeyPath é um tipo da biblioteca padrão do Swift que representa um caminho para uma propriedade de um objeto. Ao contrário do acesso manual a uma propriedade com notação de ponto (object.property) ou uma chave de string (value(forKey:)), KeyPath é um objeto de primeira classe: pode ser passado como argumento, armazenado em uma variável e composto.
Um KeyPath é escrito com uma barra invertida seguida pelo caminho para a propriedade: \Person.name. O caminho pode ser aninhado: \Person.address.city. O compilador verifica que Person tem uma propriedade name e que name é de fato um String. Se a propriedade não existir ou o tipo estiver errado, o código não compila.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (somente leitura)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
O acesso a uma propriedade via KeyPath é feito através do subscrito object[keyPath: path]. Para propriedades somente leitura, KeyPath é usado. Se a propriedade for mutável (var), WritableKeyPath está disponível. Para tipos de referência, ReferenceWritableKeyPath permite modificar a propriedade diretamente através do KeyPath.
KeyPath tem uma hierarquia de subtipos refletindo o nível de acesso à propriedade. A hierarquia base é: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Cada subtipo adiciona capacidades.
KeyPath — somente leitura. WritableKeyPath — leitura e escrita para tipos de valor (struct). ReferenceWritableKeyPath — leitura e escrita para tipos de referência (class). Swift seleciona automaticamente o subtipo correto com base no contexto.
| Tipo | Acesso | Tipo de objeto | Mutação |
|---|---|---|---|
| KeyPath | Somente leitura | Qualquer | Não |
| WritableKeyPath | Leitura e escrita | Tipo de valor (inout) | Através de contexto mutating |
| ReferenceWritableKeyPath | Leitura e escrita | Tipo de referência | Atribuição direta |
// WritableKeyPath para tipo de valor
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath para classe
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
Uma diferença importante: para tipos de valor, a atribuição via KeyPath requer var (contexto inout), enquanto para tipos de referência apenas uma propriedade mutável é necessária. Isso está alinhado com a semântica geral do Swift: tipos de valor são passados por valor, então a mutação requer mutabilidade do contêiner.
KeyPath desempenha um papel central no Combine e SwiftUI. No Combine, o operador assign usa KeyPath para ligar o valor de um publisher à propriedade de um objeto. No SwiftUI, Binding usa KeyPath para a ligação bidirecional entre modelo e visão.
O operador assign(to:on:) aceita um ReferenceWritableKeyPath e um objeto. Quando o publisher emite um valor, ele é automaticamente escrito na propriedade especificada. Esta é uma forma declarativa de gerenciar estado sem atribuições manuais.
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)
}
}
No SwiftUI, @Binding usa KeyPath para conectar uma visão pai e filha. O pai passa um Binding
KeyPath permite escrever funções de ordenação e filtragem flexíveis e type-safe. Em vez de passar um closure cada vez, você pode passar um KeyPath para a propriedade pela qual deseja ordenar a coleção. Isso torna o código mais limpo e reduz a duplicação.
A função 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)
Esta implementação ordena por qualquer propriedade Comparable sem escrever closures. Para ordenar por sobrenome, basta mudar o KeyPath: \Person.lastName. Se for necessário ordenar por uma chave personalizada (por exemplo, comprimento do nome), um map é usado internamente — o desenvolvedor pode escrever seu próprio closure para casos não padronizados.
AnyKeyPath é o tipo base na hierarquia KeyPath que apaga informações sobre os tipos específicos de raiz e valor. AnyKeyPath não sabe em qual tipo de objeto é chamado nem qual tipo de valor retorna. Isso é útil para armazenar KeyPaths heterogêneos em coleções e para reflexão.
AnyKeyPath é usado quando você precisa armazenar uma lista de caminhos para diferentes propriedades de diferentes tipos. Por exemplo, em um configurador de UI, onde cada caminho configura uma propriedade específica, e os tipos de propriedade podem diferir. AnyKeyPath permite trabalhar uniformemente com todos os caminhos.
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 configurar propriedades via AnyKeyPath
AnyKeyPath requer conversão de tipos ao recuperar o valor, já que o tipo específico foi apagado. Para total segurança de tipos, um KeyPath
KeyPath é significativamente mais eficiente que as chaves de string do KVC (value(forKey:)). As chaves de string são resolvidas através do mecanismo de runtime do Objective-C, incluindo análise de string, busca em runtime e conversão de tipos. KeyPath é um mecanismo estático baseado em estruturas nativas do Swift.
De acordo com benchmarks, KeyPath é em média 10-20 vezes mais rápido que value(forKey:) para acesso a propriedades. A diferença se deve à ausência de busca em runtime e despacho dinâmico. KeyPath acessa diretamente o offset da propriedade na memória com otimização suficiente do compilador.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC não só é mais lento, mas também não é seguro em termos de tipos: a string “name” pode não existir e a conversão as! String pode falhar. KeyPath garante a existência da propriedade e a correção do tipo em tempo de compilação. Para seções críticas de desempenho (animações, listas com milhares de itens), KeyPath é a única escolha correta.
Perguntas frequentes
KeyPath é um ponteiro para uma propriedade de um objeto que pode ser passado como um valor normal. Em vez de escrever person.name, você cria um caminho \Person.name e o usa para ler ou escrever a propriedade em qualquer instância de Person.
KeyPath — somente leitura. WritableKeyPath — para leitura e escrita de tipos de valor (struct) através de contexto inout. ReferenceWritableKeyPath — para leitura e escrita de tipos de referência (class) sem condições adicionais.
KeyPath é amplamente usado em Combine (assign(to:on:)), SwiftUI (Binding, FocusState), ordenação de coleções (sorted(by:)), Core Data (NSSortDescriptor) e para acesso seguro a propriedades aninhadas.
Sim, KeyPath é completamente type-safe. Um erro no caminho (erro de digitação, tipo errado) é detectado em tempo de compilação, não em tempo de execução. value(forKey:) usa strings e pode falhar com NSUndefinedKeyException se a chave não existir.
Não diretamente — Swift não suporta criação dinâmica de KeyPath a partir de uma string. No entanto, você pode usar AnyKeyPath para armazenar e passar KeyPaths obtidos estaticamente. Para acesso dinâmico a propriedades por string, use value(forKey:) com o runtime do Objective-C.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também