>我有一个枚举案例数组,其中每个案例都有一个keyPath
属性,该属性返回一个与枚举大小写同名的类属性匹配的AnyKeyPath
:
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
}
在上面的循环中,我想检查 case 的keyPath
属性以查看它是否是WritableKeyPath
,如果是,则创建一个闭包,该闭包将修改键路径访问的属性。
这样做的问题是WritableKeyPath
是泛型类型。我知道Root
类型,但Value
类型几乎可以是现存的任何类型。我可以为每种最有可能的类型创建一堆案例:
if let path = element.keyPath as? WritableKeyPath<Robot, Int> {
} else if let path = element.keyPath as? WritableKeyPath<Robot, String> {
} // So on and so forth
但这既耗时又丑陋且难以维护。
我确实尝试转换为动态类型,但这给出了编译器错误(Use of undeclared type 'valueType'
):
let valueType = type(of: element.keyPath).valueType
guard let path = element.keyPath as? WritableKeyPath<Self, valueType> else {
continue
}
我可以使用类型已经符合的协议,但由于某种原因,这也失败了:
guard let path = element.keyPath as? WritableKeyPath<Robot, NodeInitializable> else {
print("bad")
continue
}
print("good")
// Output:
// bad
// bad
// bad
// bad
那么,是否有可能将AnyKeyPath
转换为WritableKeyPath
,而无需大量不应在生产中使用的解包语句或奇怪的黑客?
在玩了几个小时的代码后,我得到的最好的是:
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"]
但是,必须始终将类型作为参数传递。如果只有 Mirror 类允许我们从每个属性中获取实际类型。
如果有人仍然感兴趣,可以使用以下代码:
使将使用的所有值类型符合协议并添加此函数
protocol NodeInitializable {
internal static func write<K>(keyPath: AnyKeyPath, object: inout K, value: Any?) {
let path = keyPath as? ReferenceWritableKeyPath<K, Self> // use ReferenceWritableKeyPath for classes and WritableKeyPath for structs
object[keyPath: path!] = value as! Self // or any other conversion to convert the value you have to the type you want
}
}
然后,例如,要更改 Robot 类(有问题的类)的 name 属性,可以这样做:
// conform String to the new protocol
extension String: NodeInitializable {
}
// and then
let robot = Robot()
let nameAnyKeyPath: AnyKeyPath = Robot.name
var someNewValue: Any = "New Robot Name"
let valueType = type(of: someNewValue) as! NodeInitializable.Type
valueType.write(keyPath: nameAnykeyPath, object: &robot, value: someNewValue)
print(robot.name) // should print 'New Robot Name'