Design Patterns in Swift: Observer

      3 Comments on Design Patterns in Swift: Observer

This is a really simple pattern to implement. We'll quickly go through some theory and get down to business. Hopefully, by the end of the article, you'll be using the pattern in your projects.Continuing on our journey with design patterns, this week we’ll visit the observer pattern. This is a really simple pattern to implement. We’ll quickly go through some theory and get down to business. Hopefully, by the end of the article, you’ll be using the pattern in your projects.

The Observer Pattern

Simply put, the observer pattern is a one-to-many relationship where one object notifies one or more other objects that its state changed. Object whose state changes is called the subject. In some languages this object is called ‘observable’. The object that is listening for changes on the subject is called ‘observer’, hence the name. One subject can have many registered observers.

the observer pattern is a one-to-many relationship where one object notifies one or more other objects that its state changed

As you might have guessed, we’ll need two protocols to define our observable pattern, one for the subject and one for the observer. Take a look at the diagram below:

The observer protocol is quite simple, it’s just one method that’s being called by the observable. The observable protocol has methods to add and remove observers, and a method to notify observers. We could argue if this method is really necessary, since the observable will be the only one using it in our simple example. But it’s useful to have if you want to trigger the notification process manually.

In the cleanest form of the pattern the observer is supposed to pull data from the observable. Meaning, when the observable calls the protocol method on the observer the observer is supposed to get all the properties/data from it directly. One other popular implementation is what’s called ‘push’, where the observable is sending the data that changed to the observer. I’ll show you both implementations, and you decide which one suits your needs the best. In my opinion pull is cleaner, but maybe for you push will be more appropriate.

One thing to be very careful when implementing the observable pattern is memory leaks. In the original implementation (described here), observer and observable know about each other. They hold pointers to each other. And we all know that this spells trouble. This means that a class that created the observer will have to trigger the unregister call. We can solve this problem by using an array of weak references by using NSPointerArray or an array of custom enums with weak pointers… This is a whole other blog article πŸ™‚ Let’s get back on track and write some code…

The Code

In the code example we’ll have a vehicle that will be our observable. The vehicle will notify its observers when a driver connects/disconnects from a vehicle. A lot of things can serve as an observer (view controllers, other controllers, even other observables). In our example we’ll just have two different controllers acting as observers. As we mentioned before, we’ll need two simple protocols to implement this pattern.

The Observable Protocol

protocol VehicleObservable: class {
    var driver: String? { get }
    var eventDate: Date? { get }
    var connectionState: ConnectionState { get }
    
    func addObserver(observer: VehicleObserver)
    func removeObserver(observer: VehicleObserver)
    func notifyObservers()
}

We already mentioned some of the methods of this protocol. Add and remove are for adding and removing observers πŸ™‚ ‘notifyObservers’ will notify all the observers about the current state of the observable. We have a couple of properties at the top that are supposed to hold the current state of the vehicle.

Here you have an option. If you’re going to use the pull method, then you need to have these instance variables, but if you’re going to use the push method, you can easily pull these into a separate object. In the example we’ll show both approaches.

The Observer Protocol

Vehicle observer protocol will be implemented by classes that will act as observer of the vehicle. The protocol has only one function:

protocol VehicleObserver: class {
    func notifyChangedConnection(vehicle: VehicleObservable, event: ConnectionEvent?)
}

In this function we’re passing in the observable that called the function, and we’re passing in a ‘ConnectionEvent’ object. Connection event is optional and if you’re using the pull method, you don’t have to use it. We’ll be using it here just to demonstrate both principles.

Some Utility Code

We’ll be pushing data into the observer, so we need a protocol for the data object, it’s a simple one:

protocol ConnectionEvent {
    var driver: String { get}
    var date: Date { get }
    var state: ConnectionState { get }
}

Should be clear enough, right πŸ™‚

We’ll be using an enum that will hold our connection state:

enum ConnectionState {
    case unknown
    case connected
    case disconnected
}

That’s it for the protocols (and enum πŸ™‚ ), let’s go and implement it all…

Vehicle Observable

Let’s see the code first, then we’ll go over it:

class Vehicle: VehicleObservable {
    
    var driver: String?
    var eventDate: Date?
    var connectionState: ConnectionState = .unknown
    
    private var observers: [VehicleObserver] = []
    
    func addObserver(observer: VehicleObserver) {
        if observers.contains(where: { $0 === observer }) == false {
            observers.append(observer)
        }
    }
    
    func removeObserver(observer: VehicleObserver) {
        if let index = observers.index(where: { $0 === observer }) {
            observers.remove(at: index)
        }
    }
    
    func notifyObservers() {
        let event = getConnectionEvent()
        observers.forEach { (observer) in
            observer.notifyChangedConnection(vehicle: self, event: event)
        }
    }
    
    private func getConnectionEvent() -> ConnectionEvent? {
        var event: ConnectionEvent?
        if let driver = self.driver, let date = self.eventDate {
            event = ConnectionData(driver: driver, date: date, state: connectionState)
        }
        return event
    }
    
    func connect(driver: String) {
        self.driver = driver
        self.eventDate = Date()
        self.connectionState = .connected
        
        notifyObservers()
    }
    
    func disconnect() {
        self.eventDate = Date()
        self.connectionState = .disconnected
        
        notifyObservers()
    }
}

fileprivate struct ConnectionData: ConnectionEvent {
    var driver: String
    var date: Date
    var state: ConnectionState
}

Add and remove observer methods are self-explanatory. We’re saving our observers into an array. If you remember, we mentioned earlier that this might lead to a memory leak if you’re not careful. Depending on your use-case you might want to replace this array with a weak collection. I’ll leave this as an exercise to you πŸ™‚

‘notifyObservers’ method is simply iterating over the array and calling the observer method. We’re also creating a ‘ConnectionEvent’ here and passing it to the observer. This step is optional, you don’t need to create this object at all if you don’t want/need to (if you’re not using the push method).

Those were the methods that are related to managing observers. Obviously, the vehicle class is supposed to do something. In our fictive example we’ll have a method that connects a driver to a vehicle, and a corresponding disconnect method. If you look at these methods, they’re pretty simple. Both of them manage the state of the class, and notify the observers when they’re finished. These methods depend heavily on your use-case, and this is a simple example, please don’t beat me with a stick if you find it funny πŸ˜€

That covers the vehicle observable, let’s move on to the observer.

Vehicle Observer

We’re going to implement this protocol on two slightly different classes, to illustrate some points. We’ll look at the ‘pull’ implementation first.

Pull Controller

Here’s the code:

class PullController: VehicleObserver {
    
    private var driver: String?
    private var connectionDate: Date?
    private var connectionState: ConnectionState?
    
    weak var observable: VehicleObservable? {
        willSet {
            observable?.removeObserver(observer: self)
            
            if let value = newValue {
                value.addObserver(observer: self)
            }
        }
    }
    
    func notifyChangedConnection(vehicle: VehicleObservable, event: ConnectionEvent?) {
        self.driver = vehicle.driver
        self.connectionDate = vehicle.eventDate
        self.connectionState = vehicle.connectionState
        
        print("Pull Controller: ", driver, connectionDate, connectionState)
    }
}

In this class we’re implementing the ‘VehicleObserver’ protocol. We’ll be using all the properties from the ‘VehicleObservable’ and we’ll hold a reference to the observable. We’re using the property observers here to register ourselves as an observer with the vehicle observable (say observer one more time… hint: pulp fiction πŸ˜€ ).

In the ‘notifyChangedConnection’ we’re only using the ‘vehicle’ parameter (‘pull’ methodology) to set up our instance variables.

One thing to note about holding a reference to the observable. The only reason we want to hold this in our class is so the class itself can add/remove itself as an observer. You can easily use the parent of your class to add/remove observers. Here we’re trying to stick to the original pattern as much as possible. If you’re mindful about the potential memory leak, or even better, if you use a weak collection in the observable, you’ll be grand.

Push Controller

‘PushController’ is similar to the ‘PullController’:

class PushController: VehicleObserver {
    private var driverName: String?
    private var timestamp: String?
    
    private weak var observable: VehicleObservable?
    
    init(withObservable observable: VehicleObservable) {
        self.observable = observable
        self.observable?.addObserver(observer: self)
    }
    
    func cleanup () {
        self.observable?.removeObserver(observer: self)
    }
    
    func notifyChangedConnection(vehicle: VehicleObservable, event: ConnectionEvent?) {
        self.driverName = event?.driver
        self.timestamp = event?.date.description
        
        print("Push Controller: ", driverName, timestamp)
    }
}

Just for the fun, we won’t be using all the properties from the vehicle observable. And we’ll be setting our observable through the initialiser. We also need a ‘cleanup’ method to remove ourselves as the observer. This method would be called internally. Let’s say you’re implementing this in a view controller, you could call the cleanup method in the viewWillDisappear, for example.

‘notifyChangedConnection’ implementation is similar to the ‘PullController’, the difference is, we’re using the object that’s being pushed, instead of the instance of the observable. If you need to keep track of the history of changes, I guess this would be a good use-case and an argument to use the ‘push’.

Bringing it All Together

Now we have all the code pretty much set up. Let’s quickly see how to use it:

// Setup
let vehicle = Vehicle()
        
let pullController = PullController()
pullController.observable = vehicle
        
let pushController = PushController(withObservable: vehicle)

// Use        
vehicle.connect(driver: "Bruce")
vehicle.connect(driver: "Batman")
vehicle.disconnect()

// Cleanup        
pullController.observable = nil
pushController.cleanup()

We obviously create the vehicle. We create our ‘pullController’ and set its observable using the property. After that we create the ‘pushController’ and set the observable using the initialiser.

If you start using the vehicle after that (connecting/disconnecting) you’ll see that both classes are receiving callbacks from the vehicle observable.

The cleanup code is slightly different. If you remember, in the pull controller we’re using the property observers, so we’re nilling out the reference in order to remove ourself as the observer. In the push controller we’re calling the cleanup method. I’m just showing you two possible approaches here. But to be honest with you, I much prefer the cleanup method because it’s a bit clearer in its intention.

This has been a long post, let’s wrap it up.

Conclusion

This was a long post about a simple pattern, right πŸ™‚ This is a very useful pattern that you’ll most definitely end up using in your projects. I hope you have a better understanding of it and that this post cleared it up for you even just a little.

As always… Have a nice day πŸ™‚

Dejan.

More resources

3 thoughts on “Design Patterns in Swift: Observer

  1. lovekesh bhagat

    Nicely explained, if i am not wrong you used delegation to implement observer pattern, good thing about your article is that you explained the core concept of observer.

    Reply
    1. Dejan Agostini Post author

      Hi Lovekesh,
      Thanks πŸ™‚ The article explains the textbook version of the observer pattern. Check out one of the books I referenced. The differences between observer, delegation and publish subscribe are subtle, but they are there. You’ve given me an idea for the next article πŸ™‚

      Reply
  2. Pingback: 2018: Year in Review | agostini.tech

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.