2017-06-23 103 views
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) 
    } 
} 

回答

1

根據公認的建議SE-0161 Smart KeyPaths: Better Key-Value Coding for Swift,你需要使用ReferenceWritableKeyPath寫一個值的關鍵路徑引用語義對象,採用分腳本。

(你需要傳遞一個經典String基於關鍵路徑setValue(_:forKeyPath:),不KeyPath。)

而且一些:

  • ValueValue2需要對分配相同
  • 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) 
    } 
} 
+0

這需要我更加接近。它現在編譯並且爲這個級別的類工作,但不幸的是我不需要完成最終的遊戲。我真的想把這個管道推到一個超類中,所以使用變得非常簡單。我會在上面更新我的問題,以顯示我想要實現的更詳細的結構。 –

+0

BindBase的更新很好,現在允許我使用一個非常簡單的單線程綁定調用。 –