Swift の KeyPath を使った Binding
- Apple
- 開発
Cocoa Bindings は Objective-C 時代にできたものだから String
でプロパティのキーパスを指定するけど、 Swift 4 の KeyPath
と Generics を組み合わせればすっきりしたコードで書けるのではないかと思いついたので実験。
protocol KeyPathBasedBinding {}
extension KeyPathBasedBinding
{
/// When the returned object is deinited or invalidated, it will stop binding.
/// - Parameter targetKeyPath: The KeyPath for target property. Target property must be observable through KVO.
func bind<Value, Target>(_ keyPath: ReferenceWritableKeyPath<Self, Value>, to targetKeyPath: KeyPath<Target, Value>, of target: Target) -> NSKeyValueObservation where Self: AnyObject, Target: _KeyValueCodingAndObserving
{
return target.observe(targetKeyPath, options: [.initial, .new], changeHandler: { [weak self] (sender, change) in
self?[keyPath: keyPath] = target[keyPath: targetKeyPath]
})
}
/// When the returned object is deinited or invalidated, it will stop binding.
/// - Parameter targetKeyPath: The KeyPath for target property. Target property must be observable through KVO.
func bind<Value, Target>(_ keyPath: ReferenceWritableKeyPath<Self, Value>, to targetKeyPath: KeyPath<Target, Optional<Value>>, of target: Target, nullValue: Value) -> NSKeyValueObservation where Self: AnyObject, Target: _KeyValueCodingAndObserving
{
return target.observe(targetKeyPath, options: [.initial, .new], changeHandler: { [weak self] (sender, change) in
self?[keyPath: keyPath] = (target[keyPath: targetKeyPath] ?? nullValue)
})
}
}
extension NSObject: KeyPathBasedBinding {}
Cocoa Bindings だと bind(_,to:,withKeyPath:,options)
の第一引数には自身のプロパティのキーもしくは特別に用意された NSBindingName
の定数を使用することができるけど、今回再現しているのは前者だけだ。
使用例
class Nya: NSObject
{
@objc dynamic var name: String = ""
}
func test1()
{
let button = NSButton()
button.title = "Apple"
assert(button.title == "Apple")
let nya = Nya()
nya.name = "Orange"
let binding = button.bind(\NSButton.title, to: \Nya.name, of: nya)
defer { binding.invalidate() }
assert(button.title == "Orange")
nya.name = "Banana"
assert(button.title == "Banana")
}
func test2()
{
let workspace = NSWorkspace.shared
let nya = Nya()
let binding = nya.bind(\Nya.name, to: \NSWorkspace.frontmostApplication?.localizedName, of: workspace, nullValue: "null")
defer { binding.invalidate() }
print(nya.name)
}
test1()
test2()
KeyPath
ベースになった observe(...)
と同様、必要なくなるまでは返り値を保持するのをお忘れなく。
Share
リンクも共有もお気軽に。記事を書くモチベーションの向上に役立てます。