使用keyPath绑定2属性(观察)
我想创建一个例程来简化绑定一个属性到另一个,一个非常常见的操作。我使用基于国际志愿者组织在斯威夫特4和9的XCode使用keyPath绑定2属性(观察)
我希望能够写出下面的使用绑定两个变量及其相应的keyPath
块:
self.bind(to: \BindMe.myFirstName, from: \BindMe.person.firstName)
这是一个简化的例子,正在产生各种我无法解决的编译错误。这可能是密钥路径的错误传递到func bind
,但使用keyPath的setValue
也无法编译。请参阅代码中的注释以了解我遇到的编译错误。
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)
}))
}
}
编辑
的answer below有助于初始编译问题。然而,为了实现这个功能,我需要能够将管道插入到超类中,如下所示。这将使用它很简单的把课,但我仍然编译错误挣扎:
Cannot invoke 'bind' with an argument list of type '(to: WritableKeyPath<PersonWatcher, PersonWatcher>, from: WritableKeyPath<PersonWatcher, PersonWatcher>)'
如果我通过一个通用的类型T的绑定程序,我得到这个错误,而不是:
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)
}
}
根据公认的建议SE-0161 Smart KeyPaths: Better Key-Value Coding for Swift,你需要使用ReferenceWritableKeyPath
写一个值的关键路径引用语义对象,采用分脚本。
(你需要传递一个经典String
基于关键路径setValue(_:forKeyPath:)
,不KeyPath
。)
而且一些:
-
Value
和Value2
需要对分配相同 -
T
需要表示的类型self
- KVC/KVO目标属性需要为
@objc
-
BindMe.init(person:)
需要super.init()
所以,你BindMe
会是这样的:
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
喜欢的事看起来很合理,所以我做了一些尝试。
为了满足
-
T
需求代表的类型self
(其中T == KeyPath.Root
),使用Self
将是最本能的,但不幸的是,它的使用还是很禁区当前版本的Swift。
可以的bind
定义移动到使用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)
}
}
这需要我更加接近。它现在编译并且为这个级别的类工作,但不幸的是我不需要完成最终的游戏。我真的想把这个管道推到一个超类中,所以使用变得非常简单。我会在上面更新我的问题,以显示我想要实现的更详细的结构。 –
BindBase的更新很好,现在允许我使用一个非常简单的单线程绑定调用。 –