什么是KeyPath的用途?

15

在Swift 4中,Foundation团队的许多人讨论了与Swift 3相比使用keyPath更容易的程度。这引发了一个问题...什么是keyPath?说真的,我找不到任何清晰的资源。


2
https://developer.apple.com/documentation/swift/key_path_expressions;https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/BasicPrinciples.html#//apple_ref/doc/uid/20002170-BAJEAIEE - jscs
1
我喜欢这篇博客文章:https://www.klundberg.com/blog/swift-4-keypaths-and-you/ - pushkarnk
1
https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md - pushkarnk
2个回答

18

Objective-C具有动态引用属性而不是直接引用的能力。这些引用被称为键路径。它们与直接属性访问不同,因为它们实际上并没有读取或写入值,而只是将其存储以供使用。

让我们定义一个名为Cavaliers的结构体和一个名为Player的结构体,然后创建每个结构体的一个实例:

// an example struct
struct Player {
    var name: String
    var rank: String
}

// another example struct, this time with a method
struct Cavaliers {
    var name: String
    var maxPoint: Double
    var captain: Player

    func goTomaxPoint() {
        print("\(name) is now travelling at warp \(maxPoint)")
    }
}

// create instances of those two structs
let james = Player(name: "Lebron", rank: "Captain")
let irving = Cavaliers(name: "Kyrie", maxPoint: 9.975, captain: james)

// grab a reference to the `goTomaxPoint()` method
let score = irving.goTomaxPoint

// call that reference
score()

最后几行创建了一个对goTomaxPoint()方法的引用,命名为score。问题在于,我们无法创建对船长姓名属性的引用,但keypath可以实现。


let nameKeyPath = \Cavaliers.name
let maxPointKeyPath = \Cavaliers.maxPoint
let captainName = \Cavaliers.captain.name
let cavaliersName = irving[keyPath: nameKeyPath]
let cavaliersMaxPoint = irving[keyPath: maxPointKeyPath]
let cavaliersNameCaptain = irving[keyPath: captainName]
请使用Xcode 9或相应的快照进行测试。

1
如果还有人在这里 - 你不能只是做irving.captain.name并得到勒布朗吗? - PruitIgoe
是的,但是使用它,您可以将其传递(作为下标引用) - Siempay

0

Swift键值编码(KVC)

官方文档 - 键路径表达式

[Objective-C键值编码(KVC)与键值观察(KVO)]

KeyPath(键路径)是对类型属性引用(而不是)。它为语言增添了一种动态性。

以下是其中一些:

  • KeyPath<Root, Value> - 只读
  • WritableKeyPath<Root, Value> - 对于var属性可读/写
  • ReferenceWritableKeyPath<Root, Value> - 仅对引用类型可读/写[关于]
  • PartialKeyPath<Root> - 只读,Value可以是任意类型
  • AnyKeyPath - 只读,Root可以是任意类型,Value可以是任意类型

KeyPath由以下主要部分组成:Value Path、Root Type和Value Type(属性类型)

//Value Path is SomeClass.someVariable.count
//Root Type is SomeClass
//Value Type is Int as a result of .count
let customKeyPath: KeyPath<SomeClass, Int> = \SomeClass.someVariable.count

//read
let count1 = someClass1[keyPath: customKeyPath]
let count2 = someClass2[keyPath: customKeyPath]

你可以发现,KeyPath 用于快捷方式(例如排序、迭代、过滤)、KVO[示例]、MemoryLayout[示例]、SwiftUI 和其他更高级的功能。在动画中使用 #keyPath,例如:let colorsAnimation = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.colors)) 示例:
let arr: [SomeClass] = []

//sort from iOS v15
let result1 = arr.sorted(using: KeyPathComparator(\.someVariable, order: .reverse))

//map
let result2 = arr.map(\.someVariable)
//instead of arr.map { $0.someVariable }

语法:

class SomeClass {
    @objc
    var someVariable: String = "Hello World!"
}

let keyPath1: String = #keyPath(SomeClass.someVariable)
assert(keyPath1 == "someVariable")

//let keyPath2: KeyPath<SomeClass, String> = \SomeClass.someVariable
//let keyPath2: WritableKeyPath<SomeClass, String> = \SomeClass.someVariable
let keyPath2: ReferenceWritableKeyPath<SomeClass, String> = \SomeClass.someVariable
//let keyPath2: PartialKeyPath<SomeClass> = \SomeClass.someVariable
//let keyPath2: AnyKeyPath = \SomeClass.someVariable


let someClass = SomeClass()

//read
let res = someClass[keyPath: \SomeClass.someVariable] //or just \.v(from context)
//or
//let res = someClass[keyPath: keyPath2]
assert(res == "Hello World!")

//write only for writable KeyPath
//or you get build-time error:Cannot assign through subscript: 'keyPath2' is a read-only key path
someClass[keyPath: \SomeClass.someVariable] = "world is changed 1" //or just \.v(from context).
assert(someClass.someVariable == "world is changed 1")

someClass[keyPath: keyPath2] = "world is changed 2"
assert(someClass.someVariable == "world is changed 2")


Pre Swift v4 - slow and not type safe
//read
let res = someClass.value(forKeyPath: keyPath) //or forKey:

//write
someClass.setValue("Another string", forKeyPath: keyPath) //or forKey:

从反斜杠\(反斜线)开始。
let someKeyPath = \SomeClass.someVariable //KeyPath<SomeClass, String>

如果有一个已知的对象,你可以使用\.(反斜杠点)
someClass.observe(\.someVariable, options: .new) //someClass1.someVariable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接