鉴于:
struct Foo {
var bar: String = “”
var baz: Int? = nil
}
let values: [Any] = [“foo”, 1337]
let fooPaths: [PartialKeyPath<Foo>] = [\Foo.bar, \Foo.baz]
let protoFoo = Foo()
Run Code Online (Sandbox Code Playgroud)
如何创建迭代这些键路径和值的for循环,并在类型匹配两者且keypath是可写的时将值分配给键路径?
注意:它必须使用泛型来完成,因为Foo可以具有任何类型的属性(当然,除了类型Foo).
我尝试过这样的东西:
func setKeyOnRoot<Root, Value>(_ root: Root, value: Value, key: PartialKeyPath<Root>) -> Root {
var root = root
if let writableKey = key as? WritableKeyPath<Root, Value> {
root[keyPath: writableKey] = value
}
return root
}
Run Code Online (Sandbox Code Playgroud)
但由于在调用此函数之前必须将值转换为Any,因此运行时无法看到类型Value的含义.
此实现类似于您作为您尝试的方法示例提供的内容,我相信它会产生您正在寻找的结果:
struct WritableKeyPathApplicator<Type> {
private let applicator: (Type, Any) -> Type
init<ValueType>(_ keyPath: WritableKeyPath<Type, ValueType>) {
applicator = {
var instance = $0
if let value = $1 as? ValueType {
instance[keyPath: keyPath] = value
}
return instance
}
}
func apply(value: Any, to: Type) -> Type {
return applicator(to, value)
}
}
struct Foo {
var bar: String = ""
var baz: Int? = nil
}
let values: [Any] = ["foo", 1337]
let fooPaths: [WritableKeyPathApplicator<Foo>] = [WritableKeyPathApplicator(\Foo.bar), WritableKeyPathApplicator(\Foo.baz)]
let protoFoo = zip(fooPaths, values).reduce(Foo()){ return $1.0.apply(value: $1.1, to: $0) }
print(protoFoo) // prints Foo(bar: "foo", baz: Optional(1337))
Run Code Online (Sandbox Code Playgroud)
这个版本做同样的事情,但使用原始问题中指定的for循环可以用以下代码替换倒数第二行:
var protoFoo = Foo()
for (applicator, value) in zip(fooPaths, values) {
protoFoo = applicator.apply(value: value, to: protoFoo)
}
Run Code Online (Sandbox Code Playgroud)
请注意,需要某些类型擦除才能使用在同一根类型上运行但具有不同值类型的单个keyPath数组.但是,在运行时,内部的闭包WritableKeyPathApplicator
可以专门为每个keyPath强制转换为正确的原始值类型,并且如果它是错误的类型(如上面的示例代码中那样),则默默地跳过该值,否则可能会抛出错误或打印更多有用的信息到控制台等.