I have an array of enum cases, where each case has a keyPath
property, which returns an AnyKeyPath
matching the classes property with the same name as the enum case:
protocol PathAccessor: CodingKey {
var keyPath: AnyKeyPath { get }
static var allCases: [Self] { get }
init?(rawValue: Int)
}
extension PathAccessor {
static var allCases: [Self] {
var cases: [Self] = []
var index: Int = 0
while let element = Self.init(rawValue: index) {
cases.append(element)
index += 1
}
return cases
}
}
class Robot {
let name: String
var age: Int
var powered: Bool
var hasItch: Bool?
enum CodingKeys: Int, PathAccessor {
case name
case age
case powered
case hasItch
var keyPath: AnyKeyPath {
switch self {
case .name: return \Robot.name
case .age: return \Robot.age
case .powered: return \Robot.powered
case .hasItch: return \Robot.hasItch
}
}
}
init(name: String, age: Int, powered: Bool) {
self.name = name
self.age = age
self.powered = powered
}
}
for element in Robot.CodingKeys.allCases {
// Trying to implement
}
In the loop above, I want to check the keyPath
property of the case to see if it is a WritableKeyPath
, and if it is, create a closure that will modify the property that the key path accesses.
The problem with this is that a WritableKeyPath
is a generic type. I know the Root
type, but the Value
type could be almost any type in existence. I could create a bunch of cases for each of most likely types:
if let path = element.keyPath as? WritableKeyPath<Robot, Int> {
} else if let path = element.keyPath as? WritableKeyPath<Robot, String> {
} // So on and so forth
But that is time consuming, ugly, and hard to maintain.
I did try to cast to a dynamic type, but that gives a compiler error (Use of undeclared type 'valueType'
):
let valueType = type(of: element.keyPath).valueType
guard let path = element.keyPath as? WritableKeyPath<Self, valueType> else {
continue
}
I could use a protocol that the types already conform to, but for some reason, that is also failing:
guard let path = element.keyPath as? WritableKeyPath<Robot, NodeInitializable> else {
print("bad")
continue
}
print("good")
// Output:
// bad
// bad
// bad
// bad
So, is it even possible to convert an AnyKeyPath
to a WritableKeyPath
without a huge string of unwrapping statements or weird hacks that shouldn't be used in production?
After a few hours playing around with code, the best I got was this:
struct Person {
var name: String
var collection: [String]
var optionalCollection: [String]?
let birthdate: Date
fileprivate func keyPath<V>(from label: String, type: V.Type) -> WritableKeyPath<Person, V> {
print("type: \(type)")
let valuePair: [String: PartialKeyPath<Person>] = ["name": \Person.name,
"birthdate": \Person.birthdate,
"collection": \Person.collection,
"optionalCollection": \Person.optionalCollection]
return valuePair[label] as! WritableKeyPath<Person, V>
}
}
var person = Person(name: "john",
collection: [],
optionalCollection: ["gotcha"],
birthdate: Date(timeIntervalSince1970: 0))
let name = person[keyPath: person.keyPath(from: "name", type: String.self)] // john
let birthdate = person[keyPath: person.keyPath(from: "birthdate", type: Date.self)] // Jan 1, 1970
let collection = person[keyPath: person.keyPath(from: "collection", type: Array<String>.self)] // []
let optionalCollection = person[keyPath: person.keyPath(from: "optionalCollection", type: Optional<Array<String>>.self)] // ["gotcha"]
However you must always pass the type as parameter. If only the Mirror class allowed us to get the actual type from each property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With