如何将AnyKeyPath转换为WritableKeyPath?

时间:2018-01-24 16:40:11

标签: swift casting swift4 keypaths

我有一个枚举案例数组,其中每个案例都有一个keyPath属性,它返回一个AnyKeyPath匹配的classes属性,其名称与枚举大小写相同:

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
}

在上面的循环中,我想检查案例的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是否可以可能

2 个答案:

答案 0 :(得分:0)

经过几个小时的代码处理,我得到的最好结果是:

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类允许我们从每个属性中获取实际类型。

答案 1 :(得分:0)

如果仍然有人感兴趣,则可以使用以下代码:

使您将要使用的所有值类型符合协议并添加此功能

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'