@derekli66

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