KeyPath es una ruta tipada a una propiedad de un objeto en Swift, representada como un valor de primera clase. A diferencia de las claves de cadena en KVC (Key-Value Coding), KeyPath es verificado por el compilador en tiempo de compilación: el compilador conoce el tipo del objeto raíz, el tipo de la propiedad y puede garantizar que la ruta existe. KeyPath se utiliza en Combine para enlaces reactivos, en SwiftUI para enlaces bidireccionales y en la biblioteca estándar para ordenar y filtrar colecciones. Según Apple Developer, 2025, KeyPath es un bloque fundamental para la programación funcional en Swift.
Puntos clave
KeyPath es un tipo de la biblioteca estándar de Swift que representa una ruta a una propiedad de un objeto. A diferencia del acceso manual a una propiedad mediante notación de punto (object.property) o una clave de cadena (value(forKey:)), KeyPath es un objeto de primera clase: se puede pasar como argumento, almacenar en una variable y componer.
Un KeyPath se escribe con una barra invertida seguida de la ruta a la propiedad: \Person.name. La ruta puede ser anidada: \Person.address.city. El compilador verifica que Person tiene una propiedad name y que name es realmente un String. Si la propiedad no existe o el tipo es incorrecto, el código no compila.
struct Person: Codable {
let name: String
let age: Int
var isActive: Bool
}
// KeyPath (read-only)
let namePath: KeyPath<Person, String> = \Person.name
let person = Person(name: "Alice", age: 30, isActive: true)
let name = person[keyPath: namePath]
El acceso a una propiedad a través de KeyPath se realiza mediante el subíndice object[keyPath: path]. Para propiedades de solo lectura, se usa KeyPath. Si la propiedad es mutable (var), está disponible WritableKeyPath. Para tipos de referencia, ReferenceWritableKeyPath permite modificar la propiedad directamente a través del KeyPath.
KeyPath tiene una jerarquía de subtipos que refleja el nivel de acceso a la propiedad. La jerarquía base es: AnyKeyPath → PartialKeyPath → KeyPath → WritableKeyPath → ReferenceWritableKeyPath. Cada subtipo añade capacidades.
KeyPath — solo lectura. WritableKeyPath — lectura y escritura para tipos de valor (struct). ReferenceWritableKeyPath — lectura y escritura para tipos de referencia (class). Swift selecciona automáticamente el subtipo correcto según el contexto.
| Tipo | Acceso | Tipo de objeto | Mutación |
|---|---|---|---|
| KeyPath | ReadOnly | Cualquiera | No |
| WritableKeyPath | ReadWrite | Tipo de valor (inout) | Mediante contexto mutating |
| ReferenceWritableKeyPath | ReadWrite | Tipo de referencia | Asignación directa |
// WritableKeyPath for value type
var mutablePerson = Person(name: "Bob", age: 25, isActive: false)
let writablePath: WritableKeyPath<Person, Bool> = \Person.isActive
mutablePerson[keyPath: writablePath] = true
// ReferenceWritableKeyPath for class
class User: NSObject {
@objc dynamic var name: String = ""
}
let user = User()
let refPath: ReferenceWritableKeyPath<User, String> = \User.name
user[keyPath: refPath] = "Charlie"
Una diferencia importante: para tipos de valor, la asignación mediante KeyPath requiere var (contexto inout), mientras que para tipos de referencia solo se necesita una propiedad mutable. Esto se alinea con la semántica general de Swift: los tipos de valor se pasan por valor, por lo que la mutación requiere mutabilidad del contenedor.
KeyPath desempeña un papel central en Combine y SwiftUI. En Combine, el operador assign utiliza KeyPath para vincular el valor de un editor a una propiedad de un objeto. En SwiftUI, Binding usa KeyPath para la vinculación bidireccional entre el modelo y la vista.
El operador assign(to:on:) acepta un ReferenceWritableKeyPath y un objeto. Cuando el editor emite un valor, se escribe automáticamente en la propiedad especificada. Esta es una forma declarativa de gestionar el estado sin asignaciones manuales.
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)
}
}
En SwiftUI, @Binding usa KeyPath para conectar una vista padre y una vista hija. El padre pasa un Binding
KeyPath permite escribir funciones de ordenación y filtrado flexibles y type-safe. En lugar de pasar un closure cada vez, puedes pasar un KeyPath a la propiedad por la que deseas ordenar la colección. Esto hace que el código sea más limpio y reduce la duplicación.
La función 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 implementación ordena por cualquier propiedad Comparable sin escribir closures. Para ordenar por apellido, solo cambia el KeyPath: \Person.lastName. Si se necesita ordenar por una clave personalizada (por ejemplo, longitud del nombre), se usa map internamente — el desarrollador puede escribir su propio closure para casos no estándar.
AnyKeyPath es el tipo base en la jerarquía KeyPath que borra la información sobre los tipos específicos de raíz y valor. AnyKeyPath no sabe en qué tipo de objeto se invoca ni qué tipo de valor devuelve. Esto es útil para almacenar KeyPaths heterogéneos en colecciones y para reflexión.
AnyKeyPath se usa cuando necesitas almacenar una lista de rutas a diferentes propiedades de diferentes tipos. Por ejemplo, en un configurador de UI, donde cada ruta configura una propiedad específica y los tipos de propiedades pueden diferir. AnyKeyPath permite trabajar de manera uniforme con todas las rutas.
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 { }
// Allows configuring properties via AnyKeyPath
AnyKeyPath requiere conversión de tipos al recuperar el valor, ya que el tipo específico se ha borrado. Para seguridad total de tipos, siempre se prefiere un KeyPath
KeyPath es significativamente más eficiente que las claves de cadena de KVC (value(forKey:)). Las claves de cadena se resuelven mediante el mecanismo de runtime de Objective-C, que incluye análisis de cadenas, búsqueda en runtime y conversión de tipos. KeyPath es un mecanismo estático basado en estructuras nativas de Swift.
Según pruebas comparativas, KeyPath es en promedio 10-20 veces más rápido que value(forKey:) para acceder a propiedades. La diferencia se debe a la ausencia de búsqueda en runtime y despacho dinámico. KeyPath accede directamente al offset de la propiedad en memoria con suficiente optimización del compilador.
func readWithKeyPath(person: Person) -> String {
person[keyPath: \Person.name]
}
func readWithKVC(person: Person) -> String {
person.value(forKey: "name") as! String
}
KVC no solo es más lento, sino también no es seguro en cuanto a tipos: la cadena “name” puede no existir y la conversión as! String puede fallar. KeyPath garantiza la existencia de la propiedad y la corrección del tipo en tiempo de compilación. Para secciones críticas de rendimiento (animaciones, listas con miles de elementos), KeyPath es la única opción correcta.
Preguntas frecuentes
KeyPath es un puntero a una propiedad de un objeto que se puede pasar como un valor normal. En lugar de escribir person.name, creas una ruta \Person.name y la usas para leer o escribir la propiedad en cualquier instancia de Person.
KeyPath es solo de lectura. WritableKeyPath es para lectura y escritura de tipos de valor (struct) mediante contexto inout. ReferenceWritableKeyPath es para lectura y escritura de tipos de referencia (class) sin condiciones adicionales.
KeyPath se usa ampliamente en Combine (assign(to:on:)), SwiftUI (Binding, FocusState), ordenación de colecciones (sorted(by:)), Core Data (NSSortDescriptor) y para acceso seguro a propiedades anidadas.
Sí, KeyPath es completamente type-safe. Un error en la ruta (error tipográfico, tipo incorrecto) se detecta en tiempo de compilación, no en tiempo de ejecución. value(forKey:) usa cadenas y puede fallar con NSUndefinedKeyException si la clave no existe.
No directamente — Swift no admite la creación dinámica de KeyPath a partir de una cadena. Sin embargo, puedes usar AnyKeyPath para almacenar y pasar KeyPaths obtenidos estáticamente. Para acceso dinámico a propiedades por cadena, usa value(forKey:) con el runtime de Objective-C.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también