Observer Pattern on Swift with Signals

Written by raymund.cat | Published 2017/01/12
Tech Story Tags: swift | ios | software-architecture | software-design-patterns | mobile-app-development

TLDRvia the TL;DR App

An attach-and-forget library for your iOS Projects

The Observer Pattern

The Observer Patter (sometimes also called Notifications, Broadcast-Listener, or Publish-Subscribe pattern) is a communication style common in OOP languages, much like Delegation (useful on a Parent-Children relationship) and Blocks (useful on Concurrent/Asynchronous tasks). The concept is to enable objects to “listen” or “observe” data from a relatively unrelated source.

Think: radio broadcast. The source of the analog signals simply broadcasts them on air, regardless of whether anything is listening. The receivers simply listens, regardless of whether a signal is actually being sent. The same relationship is to be considered when choosing this pattern for data communication.

Signals Library

I came across this awesome library while looking for alternative methods to implement an Observer Pattern. I liked this one particularly because of its simple implementation. It basically goes like this:

  • Create a Signal Object.
  • Fire that Signal Object.
  • Subscribe to that Signal Object from anywhere in your project.

Create a Signal Object

Initialize a Signal Object and pass the data type of your broadcast ‘message’ as its parameter

let onData = Signal<(data:NSData, error:NSError)>()let onProgress = Signal<Float>()

Fire that Signal Object

Simply call the method fire(_ data: T) on your Signal Objects and it will broadcast your data to all of its subscribers.

func receivedData(receivedData:NSData, receivedError:NSError) {// Whenever appropriate, fire off any of the signalsself.onProgress.fire(1.0)self.onData.fire((data:receivedData, error:receivedError))}

Subscribe to that Signal Object

Subscribing to a Signal takes a closure with the data as parameters. It will be called whenever a data was fired.

onProgress.subscribe(on: self) { (progress) inprint("Loading progress: \(progress*100)%")}

onData.subscribe(on: self) { (data, error) in// Do something with the data}

The “attach-and-forget” usage is also very cool as it removes the hassle of observing your object’s life cycles — avoiding memory leaks due to unbound listener bindings. If you want to read more on the Signals library, follow this github link for its official documentation.

Exercise: A Mock Downloader App

In this short tutorial, let me demonstrate the use of this library and programming pattern with a dummy “file downloader” app. This app is supposed to function like a download manager. CoreData and Asynchronous Network Tasks are huge topics on their own so we will skip those and implement a dummy download task. We’ll have them broadcast their ongoing tasks for any listeners you may need.

Our target is literally this simple.

To save you the hassle of doing all the boiler plate codes of creating a Tableview, Cells and Data Sources, I’ve created a starting project which you can download here.

1. Project Overview

Okay, here’s a quick overview of our template project. We’ll be implementing a light MVP-MVVM design so expect the usual suspects (Views, Models, Presenters, Interactors, etc). Here we have a simple DownloadViewController:

Simple and straightforward. It contains our tableView (plugged in via interface builder) and its datasource and delegate methods.

It holds a set of two models, an array of DownloadableItems and a dictionary of DownloadTasks. These two automatically gets plugged in to our tableViewCells to present our download object’s name and download status if it’s currently being downloaded.

The ViewController also follows a PresenterDelegate protocol, which gives us access to receiving data from our Presenter.

Our Presenter here holds all the logic outside the view, and practically becomes the logical identity of our screen/scene/page.

Presenter has three objects to fulfill its responsibility, objects that CanFetchItems, CanDownloadItems, and CanBroadcastTasks.

For our dummy app, all of them will be fulfilled by dummy classes, of course.

CanFetchItems will be held by an ItemStore that provides an array of statically-generated objects.

CanDownloadItems will be fulfilled by a class with a dummy requestDownload(item:) method.

For now this will be held by our DownloadManager, which will mock a download request by creating a DownloadTask — an object that can hold an item’s download progress.

CanBroadcastTasks will be fulfilled by a class that holds a Signal object, specifically one that broadcasts a dictionary of DownloadTasks.

In our case, this will be held (again), by our DownloadManager since this class will be the one initiating the downloads and managing their queue of tasks.

Still following? :D Now let’s start coding.

2. Initiate the download task

First we’ll be editing our DownloadManager’s requestDownload(item:) method:

We’ll be creating a DownloadTask from the item and give it an initial progress value.

To increment our download progress’s value asynchronously, we’ll be using a Timer schedule function which will be called at every 1/4 of a second. This updates our current task queue with each increment and removes it when the task is finished.

3. Broadcast the tasks

Still in our DownloadManager, we’ll override the init() method to initiate a Timer schedule again, which will then continuously broadcast our queue of tasks.

We’ll automate our broadcast here so we don’t spam our listeners with too many calls. Our broadcasts will be consistent ticks instead of automatic firing whenever an update has been made.

4. Listen to broadcasts

Now that we have setup our broadcasters, let’s go back to our Presenter and start listening.

In our Presenter’s viewDidLoad(), add the subscribe method so we can start listening immediately after our view gets loaded. Pass the callback’s returned tasks to your Presenter’ tasks. It will then be passed to your View’s tasks, and subsequently presented to your user. Everything happens in somewhat a reactive manner.

Run the code now, and you should be able to get something like this:

Didn’t run? Grab a copy of the final project here and see if you got everything right.

What’s Next?

Well. Now that you have a working pattern implementation, try to have more Observers by adding another ViewController and Presenter. This time, with a TableView, try to show all the working DownloadTasks only

Have fun!

Before you get too comfy..

A few remarks about the topic: The Observer Pattern is a good option to avoid a lot of relationship complications (like too much delegations and callback-ceptions). It can still be abused. Here are some Don’ts to remember when broadcasting your objects:

1. Overbroadcasting updates with little significance

I guess we can say that spamming is part of the nature of this pattern. Still, be mindful about the scalability of your implementation and the fact that you are running on a limited hardware.

In our example, I placed the broadcast call outside the actual items update methods and into a dedicated timer. When you have hundreds of ongoing downloads, this would mean you would be broadcasting hundreds or thousands of download progresses per second.

This could potentially clog your Main Thread with unnecessary update calls to your views and drastically impair the usability of your app. When implementing this design, try to keep the pace as minimal as necessary.

2. Ruining your architecture for convenience

It’s tempting. You could be directly listening for broadcasts from your View Layer, past your Presenters, because you can. With this, you can just update your view without the Presenter knowing.

But if we do this, we would violate the concept of MVP. Our View starts to manage its own business models and our Presenter starts to lose control over the View. If the Presenter receives an update, it can’t be sure if its data is consistent with the ones presented on the View.

When integrating this pattern, be mindful of your architecture standards and don’t let the convenience override your own rules.

You made it all the way here!

I hope that you got what you’re looking for when you opened this article. If so, I would very much appreciate it if you would recommend this to your friends ^^

if you have any more questions/feedbacks, please feel free to drop a message!


Published by HackerNoon on 2017/01/12