Object communication in iOS with KVO



There are three common ways objects and classes can communicate in iOS: Key-Value Observation, Notification Center and Delegation. In this first part we will only look at the first pattern.

Key-Value Observation (KVO) is an Observer pattern which provides a way for objects to know when properties are changed, and they can do so by subscribing to be notified of these changes when they occur. Any object that inherits from NSObject can use this resource at no extra cost. KVO is handy when at most a handful of objects need to be observed, because there is only a single method available for observation which could easily become stuffed with too many string-based key path condition matchings we need to make inside of this method.

We will use a simple bank account example to illustrate the KVO concept. Let’s start by creating a new Single View Application. In the storyboard add a UILabel, a UITextField and a UIButton. In the ViewController class create an IBOutlet named amountTextField and another one named currentBalanceLabel, and an IBAction named submitAction() for the button. We are done with the storyboard so let’s get to the coding part.

First, create a new class named Account. We just create two properties, one a constant and another one a variable to keep track of our account balance. Then we create an init() method where we initialize the current balance to be the starting balance. Finally, we create an update() method that we can use to modify our current balance based on an amount the user would input:

let startingBalance = 100.0
var currentBalance = 0.0

override init() {
    super.init()
    currentBalance = startingBalance
}

func update(amount: Double) {
    currentBalance += amount
}

So far so good. This class works just fine as is, except other classes are not able to communicate to it just yet. We will implement a notification system so it can communicate with other classes when changes occur. Step 1 is to create a key path that can be recognized by the communicating class:

let currentBalanceKeyPath = "currentBalance"

Then, at the end of the update() method set the value of our observed property for this key path:

setValue(currentBalance, forKeyPath: currentBalanceKeyPath)

The remaining three steps are in the ViewController class. Let’s create the same key path for consistency and also create an Account object:

let currentBalanceKeyPath = "currentBalance"
var account = Account()

In viewDidLoad() update the label text with the current balance:

currentBalanceLabel.text = "Current balance: \(account.currentBalance)"

We are now ready for Step 2: register an observer for our key path property. We do this still inside the viewDidLoad() method:

account.addObserver(self, forKeyPath: currentBalanceKeyPath, options: NSKeyValueObservingOptions.Old | NSKeyValueObservingOptions.New, context: nil)

In Step 3 we write an observing method so that observer gets notification when changes occur:

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
    if keyPath == currentBalanceKeyPath {
        if (account.currentBalance < 0) {
            currentBalanceLabel.textColor = UIColor.redColor()
        } else {
            currentBalanceLabel.textColor = UIColor.blackColor()
        }
        currentBalanceLabel.text = "Current balance: \(account.currentBalance)"
    }
}

All that matters inside this method is that we check whether the method’s keyPath argument equals our own currentBalanceKeyPath and if it does, then we proceed with the work we need to do, which in our case is just using different colors for the label text, depending on when the amount is negative or positive. In the last step, Step 4, we remove the observer to make sure we don’t see any unexpected behavior later on:

deinit {
    account.removeObserver(self, forKeyPath: currentBalanceKeyPath)
}

The last thing we need to do before running the program is to configure the IBAction method we created in the beginning, so that the button can send a new amount to the update() method:

@IBAction func submitAction(sender: UIButton) {
    let amount = (amountTextField.text as NSString).doubleValue
    account.update(amount)
    amountTextField.text = nil
}

Now run the program and notice how various amounts are added/subtracted from the current balance. The label color also reflects whether we have a negative or a positive balance. The source code is available on Github.

Until next time!