read
Trying to achieve this might sound silly, as the goal of keypath is to eliminate string and be strict. But I do need it in a particular problem, where I need to keep a list of properties that has special behaviours.
struct Book {
var title: String
var rating: Int
}
// The goal is to get & set using string for the properties
var book = Book(title: "", rating: 0)
book.set("title", "Lord of the Bing")
book.get("title")
Return keypath from a string
To return PartialKeyPath
given a string, we need to exhaustively list for each of the property. While this is tedious, it is possible to make use of Swift Macros to generate such repetitive code.
extension Book {
static func keyPath(from s: String) -> PartialKeyPath<Self>? {
switch s {
case "title": return \Self.title
case "rating": return \Self.rating
default: return nil
}
}
}
Get
func `get`(_ s: String) -> Any? {
guard let kp = Self.keyPath(from: s) else { return nil }
return self[keyPath: kp]
}
Set
Setting requires duplicating the code for all the property types, as it is necessary when casting to WritableKeyPath
.
mutating func `set`(_ s: String, _ x: Any) {
guard let kp = Self.keyPath(from: s) else { return }
switch kp {
case let w as WritableKeyPath<Self, String>: if let x = x as? String { self[keyPath: w] = x }
case let w as WritableKeyPath<Self, Int>: if let x = x as? Int { self[keyPath: w] = x }
default: break
}
}
Alternative: Use reflection
I found an implementation for KeyPathListable, which uses Mirror
to provide allKeyPaths
– a dictionary of property string to keypaths.
protocol KeyPathListable {
var allKeyPaths: [String: PartialKeyPath<Self>] { get }
}
extension KeyPathListable {
private subscript(checkedMirrorDescendant key: String) -> Any {
return Mirror(reflecting: self).descendant(key)!
}
var allKeyPaths: [String: PartialKeyPath<Self>] {
var membersTokeyPaths = [String: PartialKeyPath<Self>]()
let mirror = Mirror(reflecting: self)
for case (let key?, _) in mirror.children {
membersTokeyPaths[key] = \Self.[checkedMirrorDescendant: key] as PartialKeyPath
}
return membersTokeyPaths
}
}
extension Book: KeyPathListable {}
It is a handy protocol extension.
However, you can’t set using mirror..