Mastering Swift's Development Blog

Follow Us On Twitter
  • Jon Hoffman

Observer Pattern: Protocol Oriented Design Pattern


The observer pattern is a behavior design pattern where a type maintains a list of objects, called observers, and notifies them automatically on changes to a particular state.


Problem We Are Trying To Solve

We have an application that contains a state, like a protpert, and we would like to send notifications to other parts of our application when that state changes. For example, we may be building an e-commerce site and our customers can sign up to receive an e-mail when an item comes back into stock. In this case a notification would be sent from the inventory management part of our code to the customer notification part of our code triggering the sending of the e-mails.


Our Solution

While there are a number of ways that we can create a solution for the observer pattern, we will be leveraging the built-in property observers with swift. We could also leverage Swift’s notification center for this pattern, but we chose to use the property observer because the solution was more in line with the goal of this series to illustrate protocol-oriented techniques. We would like, in a future post, to show how to implement this pattern with the notification center.


The Example

In this example, we are going to create an observer that will be notified when a property changes. For the observer pattern, we can have a one-to-many relationship where we can have many observers. For our example we will only have a one-to-one relationship, but you could change the code to have an array of observers if need be.




As usual in our design pattern tutorials, we are going to be showing the minimal amount of code necessary to demonstrate this pattern because we want the focus of this post to be on how the observer design pattern works and not on implementing the example.


Swift property observers enable us to add observers for stored properties we define or inherit as well as computed properties that we inherit. For computed properties that we create, we can use the properties setter to respond to value changes. In our example here, we will add an observer to a stored property that we are defining.


We will begin, as with most of our design patterns, with the protocol.

protocol PropertyObserver: AnyObject {
    associatedtype valueType

    func willChange(propertyName: String, newValue: valueType?)
    func didChange(propertyName: String, oldValue: valueType?, newValue: valueType?)
}

In our PropertyObserver protocol we define two functions named willChange() and didChange(). These functions will match up with the willSet and didSet observers that Swift’s property observers provide us. We also define an associated type named valueType which is used as the type for the oldValue and newValueparameters for those functions.


We could use the Any type as the type for the oldValue and newValue parameters however we prefer to use the associatedType mainly for type safety. With the Any type a developer can pass any parameter type and even mix and match them within the same function. For example, on the didChange() function, if the Any type was used, we can pass in a String value for the oldValue and an Int value for the newValue. Using generics with the associatedType will prevent this, ifyou are unfamiliar with associated types, you can read about them in our post about associated types.


You will note that we used AnyObject in the definition for the PropertyObserver protocol. This is to force our observers to be defined as reference types. This may seem counter to the use value types mantra however there is a very important reason for defining them as reference types. We will discuss this a little later on in this post.


Now let’s look at how we would create a class that would adopt this protocol.

final class PrintObserver: PropertyObserver {  
    func willChange(propertyName: String, newValue: Int?) {
        print("\(propertyName) is about to change to \(newValue)")
    }
    
    func didChange(propertyName: String, oldValue: Int?, newValue: Int?) {
        print("\(propertyName) did change from \(oldValue) to \(newValue)")
    }
}

The PrintObserver class will simply print messages to the console just before the property value changes, using the willChange() function or after the value changes using the didChange() function. We use the final keyword for the class to prevent the class from being overridden and to avoid using dynamic dispatch.


Now let’s create a type that will use the PrintObserver class to observe one of its properties.

struct MyType<T> where T: PropertyObserver, T.valueType == Int {
    weak var propertyObserver: T?
    
    var property1 = 0 {
        willSet(newValue) {
            propertyObserver?.willChange(propertyName: "property1", newValue: newValue)
        }
        didSet {
            propertyObserver?.didChange(propertyName: "property1", oldValue: oldValue, newValue: property1)
        }
    }
}

In the definition for the MyType struct, we add a generic constraint that will only allow for the use of types that’s adopt the PropertyObserver protocol where that type uses the Int type for the associated type. This is where the type safety comes in and prevents developers from passing in types that adopt the PropertyObserver protocol but use a type that is not compatible with the property to be observed.


Next we create a property named propertyObserver that is a weak reference to an object that adopts the PropertyObserver protocol. We create a weak reference because we do not want ARC to increment the reference count to this object and if this object gets deallocated by ARC, we will want to let it go. This is why we want to force the types that adopt the PropertyObserver protocol to be reference types, if we wish to deallocate the observer in another part of our code we do not want to have to manually go back and removing them as observers. If we used value types, the propertyObserver property would receive its own instance of the type and would retain it.


Finally, we use willSet and didSet property observers to call the willChange() and didChange() functions from our property observers.


We would use the property observer pattern like this

var observer = PrintObserver()
var myType = MyType<PrintObserver>()
myType.propertyObserver = observer
myType.property1 = 5

One thing to note, since we are creating a weak reference to the propertyObserver property, this code is not valid.

myType.propertyObserver = PrintObserver()

This is not valid because ARC never increments the reference count to the new PrintObserver instance therefore the reference count is 0 and it will deallocated after this code is executed.


You may be asking yourself, why don’t we just put the code in the willSet and didSet property observers, why go through the bother of all the other code. One reason is code reuse, if we are monitoring several properties, we would have to put the same code in each property that we monitor. Another reason is flexibility, with the ability for the observer to be swapped out as needed and for them to be automatically deallocated if they are no longer needed. Code reuse and flexibility are two of the founding principles of design patterns.


We could also use functions rather than protocols and observer types. One of the things you lose with the passing in a function rather than using protocols and observer types, in our opinion, is code readability. Those that tend you use functions in this way will disagree, but it is personal preference and as I mentioned it is just our opinion.


Another, bigger thing that is lost by using functions, is the ability to deallocate the observer from another part of your code. With the example that is provided here, the part of our code doing the observing, can easily deallocate the observer, when it is no longer necessary, without having any knowledge of the code it is observer. This decoupling, for mobile development where the applications generally run on a phone, may not seem like a big concern but for server side development or even doing robotic development using Swift for Arm, this is a bigger concern.


There are numerous ways you can implement various design patterns. The idea in these posts, as the title suggests, is to present the problem the pattern is designed to solve and then a solution in a protocol-oriented way.


masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt