2

我正在尝试创建一个例程来简化将一个属性绑定到另一个属性,这是一个非常常见的操作。我在 Swift 4 和 XCode 9 中使用基于块的 KVO。

我希望能够编写以下内容来使用它们对应的绑定两个变量keyPath

self.bind(to: \BindMe.myFirstName, from: \BindMe.person.firstName )

这是一个简化的示例,它会生成我无法解决的各种编译错误。很可能 keyPath 传递给 不正确func bind,但是setValue使用 keyPath 也无法编译。请查看代码中的注释以了解我遇到的编译错误。

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)
        }))
    }
}

编辑

下面的答案有助于解决最初的编译问题。但是,为了使这完全有用,我需要能够将管道推入超类,如此处所示。这将使使用它的类非常简单,但我仍在努力解决编译错误:

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)
    }
}
4

1 回答 1

1

根据接受的提议 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)
    }
}
于 2017-06-24T00:50:46.593 回答