I want to keep two properties in sync with Cocoa bindings.
In my code, you can see that I have two classes: A
and B
. I wish to keep the message
values in A
and B
instances synchronized so that a change in one is reflected in the other. I'm trying to use the bind(_:to:withKeyPath:options:)
method of the NSKeyValueBindingCreation
informal protocol. I use Swift 4.2 on macOS.
import Cocoa
class A: NSObject {
@objc dynamic var message = ""
}
class B: NSObject {
@objc dynamic var message = ""
init(_ a: A) {
super.init()
self.bind(#keyPath(message), to: a, withKeyPath: \.message, options: nil) // compile error
}
}
I get a compile error in the line where I call bind: cannot convert value of type 'String' to expected argument type 'NSBindingName'
. I get the suggestion to wrap the first parameter with NSBindingName(rawValue: )
. After applying that, I get the error type of expression is ambiguous without more context
for the third parameter.
What am I doing wrong?
I made the following example in a playground. Instead of class A and B, I used a Counter class since it is more descriptive and easier to understand.
import Cocoa
class Counter: NSObject {
// Number we want to bind
@objc dynamic var number: Int
override init() {
number = 0
super.init()
}
}
// Create two counters and bind the number of the second counter to the number of the first counter
let firstCounter = Counter()
let secondCounter = Counter()
// You can do this in the constructor. This is for illustration purposes.
firstCounter.bind(NSBindingName(rawValue: #keyPath(Counter.number)), to: secondCounter, withKeyPath: #keyPath(Counter.number), options: nil)
secondCounter.bind(NSBindingName(rawValue: #keyPath(Counter.number)), to: firstCounter, withKeyPath: #keyPath(Counter.number), options: nil)
secondCounter.number = 10
firstCounter.number // Outputs 10
secondCounter.number // Outputs 10
firstCounter.number = 60
firstCounter.number // Outputs 60
secondCounter.number // Outputs 60
Normally bindings are used to bind values between your interface and a controller, or between controller objects, or between controller object and your model objects. They are designed to remove glue code between your interface and your data model.
If you only want to keep values between your own objects in sync, I suggest you use Key-Value Observing instead. It has more benefits and it is easier. While NSView and NSViewController manages bindings for you, you must unbind your own objects, before they are deallocated, because the binding object keeps a weak reference to the other object. This is handled more gracefully with KVO.
Take a look at WWDC2017 Session 212 - What's New in Foundation. It shows how to use key paths and KVO in a modern application.