KVO on Swift's computed properties

2019-03-21 02:48发布

问题:

just wondering if this is possible in Swift 2.2, KVO on a computed property!?

ie:

   var width = 0
   var height = 0

   private var area : Double {
          get {
                 return with * height
          }
   }

   self.addOberser(self, forKeyPath: "area", ......

Would a client code modifying the with or height trigger observeValueForKeyPath?

Just checking before engaging on a mayor class refactor. KVO's syntax being as annoying as it's is not worth even a playground if someone has an answer at hand (I am assuming answer is NO)

regards! ~d

回答1:

That code won't work for two reasons:

  1. You must add the dynamic attribute to the area property, as described in the section “Key-Value Observing” under “Adopting Cocoa Design Patterns” in Using Swift with Cocoa and Objective-C.

  2. You must declare that area depends on width and height as described in “Registering Dependent Keys” in the Key-Value Observing Programming Guide. (This applies to Objective-C and Swift.) And for this to work, you also have to add dynamic to width and height.

    (You could instead call willChangeValueForKey and didChangeValueForKey whenever width or height changes, but it's usually easier to just implement keyPathsForValuesAffectingArea.)

Thus:

class MyObject: NSObject {

    dynamic var width: Double = 0
    dynamic var height: Double = 0

    dynamic private var area: Double {
        return width * height
    }

    class func keyPathsForValuesAffectingArea() -> Set<String> {
        return [ "width", "height" ]
    }

    func register() {
        self.addObserver(self, forKeyPath: "area", options: [ .Old, .New ], context: nil)
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        print("observed \(keyPath) \(change)")
    }

}

let object = MyObject()
object.register()
object.width = 20
object.height = 5

Output:

observed Optional("area") Optional(["old": 0, "new": 0, "kind": 1])
observed Optional("area") Optional(["old": 0, "new": 100, "kind": 1])


回答2:

As @Rob stated in his answer, make area dynamic to be observed from objective-c

Now add willSet { } and didSet { } for width and height properties,
inside willSet for both properties add this self.willChangeValueForKey("area") and in didSet add self.didChangeValueForKey("area");

Now observers of area will be notified every time width or height change.

Note: this code is not tested, but I think it should do what expected