问题
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?
回答1:
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.
来源:https://stackoverflow.com/questions/48427508/how-do-you-convert-an-anykeypath-to-a-writablekeypath