I am trying to create a routine to simplify binding one property to another, a very common operation. I'm using the block based KVO's in Swift 4 and XCode 9.
I want to be able to write the following to bind two variables using their corresponding keyPath
:
self.bind(to: \BindMe.myFirstName, from: \BindMe.person.firstName )
This is a simplified example that is generating various compile errors that I can't get around. It is likely the incorrect passing of the keyPath to func bind
, but the setValue
using a keyPath also fails to compile. Please see the comments in the code for the compile errors I'm getting.
class Person : NSObject
{
init( firstName:String, lastName:String )
{
self.firstName = firstName
self.lastName = lastName
}
@objc dynamic var firstName:String
@objc dynamic var lastName:String
}
class BindMe : NSObject
{
var observers = [NSKeyValueObservation]()
let person:Person
var myFirstName:String = "<no first name>"
var myLastName:String = "<no last name>"
init( person:Person )
{
self.person = person
self.setupBindings()
}
func setupBindings()
{
self.bind(to: \BindMe.myFirstName, from: \BindMe.person.firstName )
self.bind(to: \BindMe.myLastName, from: \BindMe.person.lastName )
}
// this func declaration is likely incorrect
func bind<T,Value,Value2>( to targetKeyPath:KeyPath<T,Value>, from sourceKeyPath:KeyPath<T,Value2>)
{
// Error: Generic parameter 'Value' could not be inferred
self.observers.append( self.observe(sourceKeyPath, options: [.initial,.new], changeHandler: { (object, change) in
// Error: Cannot convert value of type 'KeyPath<T, Value>' to expected argument type 'String'
self.setValue(change.newValue, forKeyPath: targetKeyPath)
}))
}
}
EDIT
The answer below helps with the initial compile problems. However, for this to be useful at all I need to be able to push the plumbing into a superclass as shown here. This will make the class using it very simple, but I am still struggling with compile errors:
Cannot invoke 'bind' with an argument list of type '(to: WritableKeyPath<PersonWatcher, PersonWatcher>, from: WritableKeyPath<PersonWatcher, PersonWatcher>)'
If I pass a generic type T to the bind routine, I get this error instead:
Type 'BindBase' has no subscript members
class BindBase :NSObject
{
var observers = [NSKeyValueObservation]()
func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<BindBase, Value>, from sourceKeyPath: KeyPath<BindBase, Value>)
{
self.observers.append(self.observe(sourceKeyPath, options: [.initial, .new], changeHandler: { (object, change) in
self[keyPath: targetKeyPath] = change.newValue!
}))
}
}
class PersonWatcher : BindBase
{
@objc dynamic var person: Person
@objc var myFirstName: String = "<no first name>"
@objc var myLastName: String = "<no last name>"
init(person: Person) {
self.person = person
super.init()
self.bind(to: \PersonWatcher.myFirstName, from: \PersonWatcher.person.firstName)
self.bind(to: \PersonWatcher.myLastName, from: \PersonWatcher.person.lastName)
}
}