Communication Patterns

Introduction

Communication patterns is a way to pass information from one class to another. There are many types of patterns; often times asked questions in an iOS Engineering interview; particularly in their use cases and when would one be preferable over the other. Communication patterns generally shine in programming paradigms like Model View Controller or Model-View-ViewModel. In this article, I’m going to talk about two communication patterns: Notifications and Observers, and Protocols and Delegates. I will explain the theory of how they work and a simple example.


Notifications and Observers

Notifications and Observers is a one to many communication pattern. In other words, one class can communicate with many different classes. In my opinion, that in and of itself is a strength in its own right. The idea is that a class will send out a notification and one or more classes with an observer will listen for a particular notification to trigger a specific action.

We do this by first posting a notification and setting a Notification.Name value. This will allow us to differentiate from other notifications in your app. We create an observer in the class that will listen for the notification by creating an addObserver property.

In the following example, we have a MainViewController and a DetailsViewController. The MainViewController will post a notification through an @IBAction with a notification name called SaveAction.

We then call the NotificationCenter.default.post() function and pass the notification name

Create the Notification


class MainViewController: UIViewController {

    @IBAction func saveAction() {
        // Create a notification name with the name SaveAction
        let notificationName = Notification.Name.init(rawValue: “SaveAction”)

        // Create a post while passing the notification name
        NotificationCenter.default.post(notificationName, object: nil)
    }
}

Create the observer


class DetailsViewController: UIViewController {

    override viewDidLoad() {
        super.viewDidLoad()

        // Create a notification name called SaveAction
        let notificationName = Notification.Name.init(“SaveAction”)

        // Create the observer while passing the notification name and the action you want to trigger 
        // when the notification is recieved.
        NotificationCenter.default.addObserver(self, selector: #selector(DetailsViewController.triggerAction), name: notificationName, object: nil)
    }

    @objc
    func triggerAction() {
        print(“Notification recieved, action has been triggered”)
    }
}

Looking at this code with a blind eye would seem normal, however, behind this code is a problem. The DetailsViewController has created a NotificationObserver when the class is instantiated. However, after you leave the class, the NotificationObserver is still listening for notifications in-spite of the deallocation of the class. Ideally, you’d want that. However, when you visit the class a second time, it would create a new NotificationObserver on top of the previously created observer. This is a memory leak. To fix this, we simply remove the observer in a deinit() function from the DetailsViewController.


deinit() {
    NotificationCenter.default.removeObserver(self)
}
            

Protocols and Delegates

Another communication pattern is Protocols and Delegates. Unlike Notifications and observers that has a one to many communication patter, protocols and delegates is a one to one communication pattern. In my experience, I’ve used this type of pattern often by passing data back from one ViewController to the next.

The idea is simple: The protocol is a separate class of predefined functions. The protocol is then inherited by a class which we will call ClassA; and the predefined functions in the protocol is defined inside ClassA. The second class we call ClassB will have a reference to the protocol from ClassA we call a delegate. We use this delegate to call the predefined functions from the protocol and pass any information necessary to ClassA. Let’s take a look at an example:

Create the protocol called ClassProtocols, ClassA, and ClassB. ClassA will inherit the ClassProtocols. In doing so, we'd have to define the functions that have been predefined in our ClassProtocols. Lastly, we create a reference to ClassB

Inside ClassB, we'd need a reference to the Protocol. This will be our delegate. We instantiate this delegate inside ClassA through our ClassB reference. Your code should look something like this.


// 1) Create the protocol
protocol ClassProtocols {
    func setNumber(num: Int)
}

// 2) ClassA inherits the protocols
class ClassA: ClassProtocols {

// 3) Create a reference to ClassB
    var classB = ClassB()

    init() {
        // 7) Instantiate the delegate from ClassB
        classB.delegate = self
    }

// 4) Define the functions from the protocol
    func setNumber(num: Int) {
        print("Number recieved", num)
    }
}

class ClassB {

// 5) Create a reference to the protocol
    var delegate: ClassProtocols!

// 6a) Create a separate function
    func settingNumber() {
// 6b) Call the delegate and the predefined function and pass a value.
        delegate.setNumber(i: 10)
    }

}
            

Conclusion

In my experience, while I know about notifications and observers, I never really used them in real applications. Mainly because I never had any reason to use notifications and observers. When I first applied it to real application, notifications and observers are generally easier to use but prone to memory leaks if not careful. If you find yourself struggling to understand these concepts, try applying them to an actual project.