Reputation: 3398
In Swift 4 many on the Foundation team have discussed how much easier it is to use keyPaths as compared to Swift 3. This begs the question... What is a keyPath? Seriously, I can't find any clear resources.
Upvotes: 16
Views: 5990
Reputation: 34311
Swift Key Value Coding (KVC)
Official doc - Key-Path Expressions
[Objective-C Key Value Coding(KVC) vs Key Value Observing(KVO)]
KeyPath
(key path) is a reference to a property of a type(rather than value). It adds a dynamism into language
There are some of them:
KeyPath<Root, Value>
- read onlyWritableKeyPath<Root, Value>
- read/write for var
propertyReferenceWritableKeyPath<Root, Value>
- read/write only for reference types[About]PartialKeyPath<Root>
- read only, Value is anyAnyKeyPath
- read only, Root is any, Value is anyKeyPath
consists of next main parts: Value Path, Root Type, Value Type(property 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]
You can find that KeyPath
is used for shortcuts(e.g. sorting, iterating, filtering), KVO[Example], MemoryLayout[Example], SwiftUI and others more advanced features. #keyPath
is used in animation for example let colorsAnimation = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.colors))
examples:
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 }
Syntax:
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:
\
(backslash)let someKeyPath = \SomeClass.someVariable //KeyPath<SomeClass, String>
\.
(backslash dot)someClass.observe(\.someVariable, options: .new) //someClass1.someVariable
Upvotes: 1
Reputation: 7445
Objective-C has the ability to reference a property dynamically rather than directly. These references, called keypaths. They are distinct from direct property accesses because they don't actually read or write the value, they just stash it away for use.
Let define a struct called Cavaliers and a struct called Player, then create one instance of each:
// 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()
The last lines create a reference to the goTomaxPoint() method called score. The problem is, we can't create a reference to the captain's name property but keypath can do.
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]
Please test with Xcode 9 or capable snapshot.
Upvotes: 19