Hi!
I am working on simple @dynamicMemberLookup class that tracks all mutation to a given object.
General idea is as simple as this:
@dynamicMemberLookup
class Writer<Value> {
internal init(value: Value, didWrite: @escaping (PartialKeyPath<Value>, Value) -> ()) {
self.value = value
self.didWrite = didWrite
}
private var value: Value
private let didWrite: (PartialKeyPath<Value>, Value) -> ()
subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> T {
get { value[keyPath: keyPath] }
set {
value[keyPath: keyPath] = newValue
didWrite(keyPath, value)
}
}
}
Then I move to the nested subscripts: It is cases where I need to compose multiple levels of tracking:
subscript<T>(nested keyPath: WritableKeyPath<Value, T>) -> Writer<T> {
Writer<T>(value: value[keyPath: keyPath]) { nestedKeyPath, new in
self.value[keyPath: keyPath] = new
let initialKeyPath = keyPath as PartialKeyPath<Value>
let fullKeyPath = initialKeyPath.appending(path: nestedKeyPath)!
self.didWrite(fullKeyPath, self.value)
}
}
All is good so far. I need to downcast WritableKeyPath to PartialKeyPath for some reason, but it is ok.
Problem is optional chaining.
Imagine structure that I need to keep track:
struct AllCounters {
var byId[Int: Counter]
}
struct Counter {
var value: Int
}
So I need to produce Writer with optional nested type:
subscript<T>(maybe keyPath: WritableKeyPath<Value, T?>) -> Writer<T>? {
guard let child = value[keyPath: keyPath] else {
return nil
}
return Writer<T>(value: child) { nestedKeyPath, new in
self.value[keyPath: keyPath] = new
let unwrappedKeyPath = keyPath.appending(path: \.self!)
let initialKeyPath = unwrappedKeyPath as PartialKeyPath<Value>
let fullKeyPath = initialKeyPath.appending(path: nestedKeyPath)!
self.didWrite(fullKeyPath, self.value)
}
}
Initiall key path is \AllCounters.byId[0]
and it is resolving into Optional<Counter>
.
My nestedKeyPath
is \Counter.value
, which is logical.
When I am trying to append nested one to initial without let unwrappedKeyPath = keyPath.appending(path: \.self!)
this force unwrapping I am receiving nil
.
Looks like KeyPath<T, U?>
and KeyPath<U, V>
should be appended and resolved to KeyPath<T, V?>
without any problems.
How can I achieve this composition?