Given:
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()
How can I make a for loop that iterates through these keypaths and values, and assigns the values to the keypaths when the type matches for both and the keypath is writable?
Note: it must be done using generics, since Foo could have properties of any type whatsoever (except type Foo, of course).
I have tried stuff like:
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
}
But since value is necessarily cast as Any before calling this, the runtime fails to see the type Value as what it is.
This implementation is similar to what you provided as an example of an approach you tried, and I believe it produces the result you are looking for:
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))
A version of this that does the same thing, but using a for loop as specified in the original question could replace the second-to-last line with:
var protoFoo = Foo()
for (applicator, value) in zip(fooPaths, values) {
protoFoo = applicator.apply(value: value, to: protoFoo)
}
Note that some type erasure is needed to work with a single array of keyPaths that operate on the same root type but have different value types. However, at runtime, the closure inside WritableKeyPathApplicator
can specifically cast to the correct original value type for each keyPath and either silently skip the value to set if it's the wrong type (as in the example code above), or it could throw an error or print more helpful information to the console, etc.