How to Convert an Anykeypath to a Writablekeypath

Appending two keypaths in Swift

First of all to use Swift 4 keypaths the objects are not required to inherit from NSObject(not even to be a class) and have KVC compliant properties.

Second of all NSObject is similar to AnyObject but Swift 4 key paths need concrete static types.

Finally the property source is optional so you have to unwrap the key path to be valid.


To be able to concatenate key paths the last component of the first key path must be of the same type as the first component of the second key path

This is the .appending Rule from WWDC 2017 - Session 212: What's new in Foundation

Sample Image

You can append key paths this way

class Foo {
var items = [1, 2, 3]
}

class Bar {
var source: Foo?
}

let bar = Bar()
bar.source = Foo()

let keyPath1 = \Bar.source!
let keyPath2 = \Foo.items
let keyPath = keyPath1.appending(path: keyPath2)

let items = bar[keyPath:keyPath]
print(items)

In Swift 4, how can you assign to a keypath when the type of the keypath and value are generic but the same?

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.



Related Topics



Leave a reply



Submit