KeyPath 并不神秘

Hi, 大家好,今天我想分享的是 Swift 中的 KeyPath。在 Swift 中,KeyPath 是一个强大的工具,它允许我们在代码中以安全的方式动态的访问对象的属性。

什么是 KeyPath

 
swift
代码解读
复制代码
class KeyPath<Root, Value>

KeyPath 是用来描述对象属性的路径,在这里我们可以把Root看成导航的起点,Value看成导航的终点,KeyPath 就是从起点到终点的路径。

举个例子:

 
swift
代码解读
复制代码
struct User {
    var name: String
    let email: String
    init(name: String, email: String) {
        self.name = name
        self.email = email
    }
}
let nameKeyPath = \User.name

上例中 User是起点,name是终点,路径是 User->name

Key-Path 表达式

一般形式

 
swift
代码解读
复制代码
\<#type name#>.<#path#>

key-path表达式可以表示属性或者是下标. 类型名是一个具体类型,路径可以是属性名称,下标,可选链表达式

 
swift
代码解读
复制代码
let keyPath = \[String].first?.count

在编译时,编译器会将key-path表达式转化成类 KeyPath

应用

我们使用下标方法 subscript(keyPath:)来访问属性。

 
swift
代码解读
复制代码
var user = User(name: "Bob", email: "Bob@sina.com")
let emailKeyPath = \User.email
print(user[keyPath: emailKeyPath) // Print Bob@sina.com

这里由于 User是结构体,且email是不变属性,这里emailKeyPath会被推断成 KeyPath, 只能够访问对象的属性值,不能修改属性值。

 
swift
代码解读
复制代码
user[keyPath: emailKeyPath] = "Muzzy" //Error: Cannot assign through subscript: 'keyPath' is a read-only key path

name是可变属性,我们writableKeyPath 被推断成WritableKeyPath 可以修改属性

 
swift
代码解读
复制代码
let writableKeyPath = \User.name
user[keyPath: writableKeyPath] = "Muzzy" // Works!

当我们将User的实例user 改成不可变实例,不能修改name属性

 
swift
代码解读
复制代码
let user = User(name: "Bob", email: "Bob@sina.com")
user[keyPath: emailKeyPath] = "Muzzy" //Error: Cannot assign through subscript: 'user' is a 'let' constant

如果我们将 User从结构体改成类,keyPath 会被推断成ReferenceWritableKeyPath, 即使user 是不可变实例,我们仍然可以改过name属性。

 
swift
代码解读
复制代码
let user = User(name: "Bob", email: "Bob@sina.com")
user[keyPath: referenceWritableKeyPath] = "Muzzy" //Works!

替代闭包或者函数的作用

当我们在一些需要使用函数或者闭包的场景下,我们可以使用闭包表示式代替闭包或者函数。具体来说,您可以使用一个key-path表达式,其根类型为 SomeType,路径生成的值类型为 Value,来替代一个类型为 (SomeType) -> Value 的函数或闭包。

 
swift
代码解读
复制代码
let usersToHire = [
    User(name: "Bob", email: "Bob@sina.com"),
    User(name: "Muzzy", email: "Muzzy@163.com"),
    User(name: "Corvax", email: "Corvax@google.com"),
]
let emailListFromClosure = usersToHire.map { $0.email }
let emailListFromKeyPath = usersToHire.map(\User.email)

由于 \.email的根类型是 User且它的值是String类型,符合 (User)->String,可以被转换。

KeyPath 的动态性

 
swift
代码解读
复制代码
struct Address {
    var city: String
}
struct Person {
    var name: String
    var address: Address
}
let person = Person(name: "Alice", address: Address(city: "New York"))
let shouldAccessCity = Bool.random()
let keyPath: KeyPath<Person, String> = shouldAccessCity ? \Person.address.city : \Person.name
print(person[keyPath: keyPath]) // 根据条件动态访问 `city` 或 `name`

利用KeyPath的动态性,我们可以在运行时控制我们对具体属性的访问。

结语

感谢大家,欢迎点赞收藏。

posted on 2025-04-30 01:43  漫思  阅读(34)  评论(0)    收藏  举报

导航