Tuesday, May 5, 2015

Swift and the Observer Design Pattern

The Observer pattern is one of the design patterns that I tend to use the most.  The basic principle behind this pattern is a listener registers with a broadcaster and then at some later point in time the broadcaster notifies the listener when some predefined event happens.  This pattern can be used to facilitate the communication between decoupled objects and also to implement a one-to-many relationship.  The Observer pattern falls into the behavior pattern category.

Apple has provided us with the NSNotificationCenter class (or simply notification center) in the Cocoa library that implements the Observer pattern.  There are three primary functions that we can perform with this class.   They are:
  • Add an Observer
  • Notify the Observer
  • Removing an Observer

·
Lets start off by looking at how we would add an observer

Adding an Observer

In the examples for this post, we will be using the following function to add an observer:

func addObserver(notificationObserver: AnyObject,
        
selector notificationSelector: Selector,
           
name notificationName: String?,
         
object notificationSender: AnyObject?)

This function has the following parameters:
  •  notificationObserver:  This is the object registering as the observer.
  •   notificationSelector:  This is the function to call when the notification is posted.  This method must accept one argument which is an instance of the NSNotification class.
  •  NotificationName:  This is the name of the notification that we wish to register with.  We generally want to define this name as an application constant so if we ever need to change the name we only need to change it in one spot.  If this value is nil, the notification center will not use the name to determine if an observer should be notified when a notification is posted.
  •  notificationSender:  This is the object which the observer wishes to receive notifications from.  If this value is nil, the notification center will not use the sender to determine if an observer should be notified when a notification is posted.

Now lets look at how we would use this function to add an observer:

NSNotificationCenter.defaultCenter().addObserver(self, selector: "actOnButton1:", name: Constants.BUTTON_1_CLICK_NOTIFY, object: nil)

In this example, we are adding a new observer where the notificaitonObserver is the current object, the notificationSelector is a function named actOnButton1, the notificationName is defined by the BUTTON_1_CLICK_NOTIFY property of the Constants class and the notificationSender is nil.

Now lets look at how we would notify the observers.

Notify an Observer

In the examples for this post we will be using the following function to post a notification to the observers.

func postNotificationName(notificationName: String,
                   
object notificationSender: AnyObject?)

This function has the following parameters:
  •  notificationName:  This is the name of the notification we wish to post.  We generally want to define this name as an application constant so if we ever need to change the name we only need to change it in one spot. 
  •  notificationSender:  The object which is posting the notification.

Lets look at how we would use this function to post a notification.

NSNotificationCenter.defaultCenter().postNotificationName(Constants.BUTTON_1_CLICK_NOTIFY, object: self)

In this example, we are posting a new notification where the notificationName is defined by the BUTTON_1_CLICK_NOTIFY property of the Constants class and the notificationSender is the current object

Before we put it all together in our example, we need to show one more function of the notification center.  This is how to remove an observer.

Removing an Observer

In the examples for this post we will be using the following function to remove an observer.

func removeObserver(notificationObserver:AnyObject)

This function takes one parameter, which is:
  •  notificationObserver:  The object that is registered to receive the notification.

Here is an example of how to use this function to remove an observer.

NSNotificationCenter.defaultCenter().removeObserver(self)

In this example we are removing the notifications for the current object.  Now lets show how we would put these together to use in our applications.

NSNotification Example

In our example, the main screen will have two buttons where each button will send a different notification when they are tapped.  We will then have two observer classes that will register for the notifications and print messages to the console when the notifications are received.  The first thing we need to do is to crate a constant class that will contain the notification names.

Constants.swift

Here is the code for our Constants.swift class

class Constants {
   
    static let BUTTON_1_CLICK_NOTIFY = "hoffman.jon.button.1"
    static let BUTTON_2_CLICK_NOTIFY = "hoffman.jon.button.2"
   
}

In this class, all we do is define two static constants that define the names of our notifications.  If we ever need to rename our notifications, we can rename it in this one spot rather than all over our code.

Observer Classes

We will create two observer classes.  These will be named Observer1.swift and Observer2.swift.  These observer classes will subscribe to receive the notifications when our buttons are pressed.
The Observer1.swift class will subscribe to the notification named BUTTON_1_CLICK_NOTIFY, while the Observer2.swift class will subscribe to both the BUTTON_1_CLICK_NOTIFY and BUTTON_2_CLICK_NOTIFY notifications.

Lets look at the code for the Observer1.swift class first.

import Foundation

class Observer1{

    init() {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "actOnButton1:", name: Constants.BUTTON_1_CLICK_NOTIFY, object: nil)
    }
   
    @objc func actOnButton1(notification: NSNotification) {
        println("Button 1 pressed from Observer1")
    }
   
    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
   
}

In the Observer1.swift class we start off by creating an initiator.  In that initiator we use the NSNotification.defaultCenter() .addObserver() function to subscribe to the notification that named with the Constants.BUTTON_1_CLICK_NOTIFY constant.  When we subscribe to the notification we set the selector to the actOnButton1: function.  This function will be called every time a BUTTON_1_CLICK_NOTIFY notification is published.

In the actOnButton1() function, we simply write a message to the console to let us know when the function is called.  Notice that we use the @objc attribute with this function.  The notification center requires that we use this attribute with any function that we want to use to receive notifications.

Finally we define a de-initiator for our Observer1.Swift class.  In this de-initiator we use the NSNotificationCenter.defaultCenter().removeObserver() function to remove the observer.  We want to make sure that we remove the observer when we destroy an instance of the class.

Now lets look at the Observer2.swift class.

import Foundation

class Observer2 {

    init() {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "actOnButton1:", name: Constants.BUTTON_1_CLICK_NOTIFY, object: nil)
       
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "actOnButton2:", name: Constants.BUTTON_2_CLICK_NOTIFY, object: nil)
    }
   
    @objc func actOnButton1(notification: NSNotification) {
        println("Button 1 pressed from Observer2")
    }
   
    @objc func actOnButton2(notification: NSNotification) {
        println("Button 2 pressed from Observer2")
    }
   
    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

The Observer2.swift class is pretty much the same as the Observer1.swift class except that we subscribe to both the BUTTON_1_CLICK_NOTIFY and the BUTTON_2_CLICK_NOTIFY notifications.

Finally lets look at the ViewController class that will publish the notifications when the buttons are tapped.

ViewController.swift

The Viewcontroller class contains two functions that are tied to UIButtons.  These functions will publish the notifications when the buttons are tapped.  These functions are shown here:
@IBAction func button1Pressed(sender: UIButton) {
        NSNotificationCenter.defaultCenter().postNotificationName(Constants.BUTTON_1_CLICK_NOTIFY, object: self)
    }
   
    @IBAction func button2Pressed(sender: UIButton) {
        NSNotificationCenter.defaultCenter().postNotificationName(Constants.BUTTON_2_CLICK_NOTIFY, object: self)
    }
Within these functions, all we do is use the NSNotificationCenter.defaultCenter().postNotificationName() function to post the notifications when the buttons are tapped.  Each time a button is tapped and the notifications are posted, the functions in the Observer1.swift and Observer2.swift instances are called.

In this post we saw how easy it is to implement the Observer pattern in Swift using the notification center that Apple provides.  I hope this post helps you understand both the notification center and the Observer pattern.

You can read more about design patterns in Swift in my forthcoming book Mastering Swift published by Packt Publishing.



3 comments:

  1. You can also use String-based enums in Swift instead of the less expressive Constants class to hold on to constant string values: http://brettbukowski.github.io/SwiftExamples/examples/enums/

    Makes sense for some event names, less so for others.

    ReplyDelete
  2. This blog awesome and i learn a lot about programming from here.The best thing about this blog is that you doing from beginning to experts level.

    Love from

    ReplyDelete
  3. Thank you. This helped. But I needed the notification to go between the DataModel change and a observing ViewController. But I got this to work.. Thanks again

    ReplyDelete