The Missing Key in Block-Based KVO on Swift
The new block-based KVO in Swift is really great and easy to use. We just need to use observe(_:options:changeHandler:)
to register a change event and write code in the changeHandler
block (closure) to handle the reaction.
But, there is one thing we have to be careful about. If we write the code below:
_ = product.observe(\.name, options: [.new, .old]) { (product, change) in
change.newValue.map { print("We have new value \($0)") }
}
product.name = "JamCrazy"
You will not get notified when the value changes. However, we can try this approach. It could be the official way:
let observation = product.observe(\.name, options: [.new, .old]) { (product, change) in
change.newValue.map { print("We have new value \($0)") }
}
product.name = "JamCrazy"
The above code will trigger the change event on the name
property. Is the setup complete? Not yet.
If we look into the definition in Foundation, we will notice something special:
public class NSKeyValueObservation: NSObject {
/// invalidate() will be called automatically when an NSKeyValueObservation is deinited
public func invalidate()
}
NSKeyValueObservation is the object that observe(_:options:changeHandler:)
returns. From the comment inside NSKeyValueObservation, we can see that if we don’t retain this object, the changeHandler
block will be invalidated.
So, the correct way would be like this: Use a property to retain NSKeyValueObservation or keep it inside a collection object.
let observation = product.observe(\.name, options: [.new, .old]) { (product, change) in
change.newValue.map { print("We have new value \($0)") }
}
// Must retain the NSKeyValueObservation object,
// otherwise invalidate() will be called automatically
observations.append(observation) // `observations` is an NSKeyValueObservation array