使用 keyPath 绑定 2 个属性(观察) [英] Binding 2 properties (observe) using keyPath
问题描述
我正在尝试创建一个例程来简化将一个属性绑定到另一个属性的过程,这是一个非常常见的操作.我在 Swift 4 和 XCode 9 中使用基于块的 KVO.
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.
我希望能够编写以下代码来使用它们对应的 keyPath
绑定两个变量:
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 )
这是一个简化的示例,它会生成我无法绕过的各种编译错误.很可能将 keyPath 错误地传递给 func bind
,但是使用 keyPath 的 setValue
也无法编译.请参阅代码中的注释,了解我遇到的编译错误.
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)
}))
}
}
编辑
下面的答案有助于解决初始编译问题.但是,为了使这完全有用,我需要能够将管道推入超类,如下所示.这将使使用它的类变得非常简单,但我仍然在为编译错误而苦苦挣扎:
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:
无法使用类型为(to: WritableKeyPath
如果我将泛型类型 T 传递给绑定例程,则会收到此错误:
If I pass a generic type T to the bind routine, I get this error instead:
类型 'BindBase' 没有下标成员
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)
}
}
推荐答案
根据已接受的提案 SE-0161 Smart KeyPaths: Better Key-Value Coding for Swift,需要使用ReferenceWritableKeyPath
给key path写一个值对于具有引用语义的对象,使用下标.
According to the accepted proposal SE-0161 Smart KeyPaths: Better Key-Value Coding for Swift, you need to use ReferenceWritableKeyPath
to write a value to the key path for an object with reference semantics, using subscript.
(您需要将经典的基于 String
的键路径传递给 setValue(_:forKeyPath:)
,而不是 KeyPath
.)
(You need to pass a classic String
-based key path to setValue(_:forKeyPath:)
, not KeyPath
.)
还有一些:
Value
和Value2
需要相同才能赋值T
需要表示self
的类型- KVC/KVO 目标属性需要为
@objc
BindMe.init(person:)
需要super.init()
Value
andValue2
need to be the same for assignmentT
needs to represent the type ofself
- KVC/KVO target properties need to be
@objc
BindMe.init(person:)
needssuper.init()
所以,你的 BindMe
应该是这样的:
So, your BindMe
would be something like this:
class BindMe: NSObject {
var observers = [NSKeyValueObservation]()
@objc let 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.setupBindings()
}
func setupBindings() {
self.bind(to: \BindMe.myFirstName, from: \BindMe.person.firstName)
self.bind(to: \BindMe.myLastName, from: \BindMe.person.lastName)
}
func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<BindMe, Value>, from sourceKeyPath: KeyPath<BindMe, Value>) {
self.observers.append(self.observe(sourceKeyPath, options: [.initial, .new], changeHandler: { (object, change) in
self[keyPath: targetKeyPath] = change.newValue!
}))
}
}
<小时>
对于编辑:
制作一个BindBase
之类的东西的需求似乎很合理,所以我做了一些尝试.
The demand to make a BindBase
like thing seems very reasonable, so I have made some tries.
履行
T
需要表示self
的类型
T
needs to represent the type ofself
(where T == KeyPath.Root
),使用 Self
将是最本能的,但不幸的是,它的使用在当前版本的 Swift 中仍然非常受限.
(where T == KeyPath.Root
), using Self
would be the most instinctive, but unfortunately, its usage is still very restricted in the current version of Swift.
您可以使用 Self
将 bind
的定义移动到协议扩展中:
You can move the definition of bind
into a protocol extension using Self
:
class BindBase: NSObject, Bindable {
var observers = [NSKeyValueObservation]()
}
protocol Bindable: class {
var observers: [NSKeyValueObservation] {get set}
}
extension Bindable {
func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<Self, Value>, from sourceKeyPath: KeyPath<Self, Value>)
where Self: NSObject
{
let observer = self.observe(sourceKeyPath, options: [.initial, .new]) {object, change in
self[keyPath: targetKeyPath] = change.newValue!
}
self.observers.append(observer)
}
}
这篇关于使用 keyPath 绑定 2 个属性(观察)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!