KeyPath 并不神秘
Hi, 大家好,今天我想分享的是 Swift 中的 KeyPath。在 Swift 中,KeyPath 是一个强大的工具,它允许我们在代码中以安全的方式动态的访问对象的属性。
什么是 KeyPath
class KeyPath<Root, Value>
KeyPath 是用来描述对象属性的路径,在这里我们可以把Root看成导航的起点,Value看成导航的终点,KeyPath 就是从起点到终点的路径。
举个例子:
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 表达式
一般形式
\<#type name#>.<#path#>
key-path表达式可以表示属性或者是下标. 类型名是一个具体类型,路径可以是属性名称,下标,可选链表达式
let keyPath = \[String].first?.count
在编译时,编译器会将key-path表达式转化成类 KeyPath
应用
我们使用下标方法 subscript(keyPath:)来访问属性。
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, 只能够访问对象的属性值,不能修改属性值。
user[keyPath: emailKeyPath] = "Muzzy" //Error: Cannot assign through subscript: 'keyPath' is a read-only key path
而name是可变属性,我们writableKeyPath 被推断成WritableKeyPath 可以修改属性
let writableKeyPath = \User.name
user[keyPath: writableKeyPath] = "Muzzy" // Works!
当我们将User的实例user 改成不可变实例,不能修改name属性
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属性。
let user = User(name: "Bob", email: "Bob@sina.com")
user[keyPath: referenceWritableKeyPath] = "Muzzy" //Works!
替代闭包或者函数的作用
当我们在一些需要使用函数或者闭包的场景下,我们可以使用闭包表示式代替闭包或者函数。具体来说,您可以使用一个key-path表达式,其根类型为 SomeType,路径生成的值类型为 Value,来替代一个类型为 (SomeType) -> Value 的函数或闭包。
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 的动态性
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的动态性,我们可以在运行时控制我们对具体属性的访问。
结语
感谢大家,欢迎点赞收藏。
浙公网安备 33010602011771号