In the following tutorial, we will build a sample iPhone app that communicates internally via Signals, a Swift library for creating and observing events.

Introduction

Plumbing, architecture, boilerplate. Whatever name you choose, finding the best way of doing the boring stuff of moving stuff around your app safely and efficiently is a problem we all have to address. Signals by Tuomas Artman via Dependency Injection is my favoured approach. A Signal is an event that is broadcast and can be listened to by other classes, and reacted to if needs be.

Dependency Injection and MVC

The cleanest way to implement Signals is to inject a singleton-with-a-small-s Signals' event bus as a "service" using Dependency Injection. Don't fret if the preceding sentence was Greek to you, I have written a tutorial about Dependency Injection here, which you might want to read first, and I will explain the rest below.

In MVC - or Model-View-Controller - driven development, apps cleanly separate their user interfaces from the data they display, and the Model is the part of any given app that deals solely with data. MVC Models are chiefly constructed from two types of classes:

  1. proxies, that store data; and
  2. services, that transport data.

What we will do is to create a single instance of a class called SignalsService, and then inject it throughout our application.

Creating SignalsProtocol

If you've digested my Dependency Injection tutorial, you will know that the first thing we need to create is a protocol for our service. Create a new Swift file called SignalsProtocol.swift and add the following code:

This is a complete list of the basic functions needed to make the bus work. Let's implement them one-by-one.

Create a new Swift file called SignalsService.swift and add the following code:

This implements the four functions in the protocol. Instances of other classes will use these four methods to listen for and stop listening for signals, and to dispatch and delete signals. It imports Signals at the top, which is Tuomas Artman's library that you will need to add to your project via Cocoapods or Carthage. It also adds the private variables _signals and _signatures, which we will use later to store our signals.

Initialise these two variables immediately thus:

Next add the following private function:

This function checks whether a given listener already exists or not, and returns a Boolean. As you can see, it calls for a class called SignalSignature, so let's create it.

Create a new Swift file called SignalSignature.swift and add the following code:

Every time a new Signal is created, a unique signature is created with it. This ensures that signals are never duplicated. A SignalSignature object requires: a String key, which identifies a particular event, and might be something like API_CALL_RETURNED; and an AnyObject listener, which would be the class instance that wishes to listen for the API_CALL_RETURNED signal. Thus each SignalSignature ensures that each class instance can only listen once for any given type of signal.

Next add the following private function:

_getSignalBy( Key ) will return an existing Signal, or create one and then return it if it doesn't already exist and the parameter createIfMissing is true. Each and every signal is identified by a key - such as API_CALL_RETURNED mentioned just above - and can have multiple listeners listening to it.

Next add the following private function:

This function will be called by the public function addListenerFor( Key ). We could of course simply put the functionality for adding a listener in addListenerFor( Key ) itself, but should later we wish to have a function that adds multiple listeners, such as addListenersFor( Keys ), it would require refactoring, so it's good to take that into account upfront.

Next edit addListenerFor( Key ) to look like this:

Let's go through what happens here:

  1. If the listener requesting to be registered already exists, the registration attempt is blocked and false is returned;
  2. assuming that the request is new, we then get or create a Signal, and a SignalSignature;
  3. if the relevant collection of signatures does not already exist in the _signatures Dictionary, it created and set locally;
  4. the signature is set in said relevant collection of signatures;
  5. the signal itself starts listening via Signal's listen() function; and
  6. either true or false is returned, depending on success or otherwise.

At this point we can add a listener to a signal. Now lets add the functionality to remove one.

Next add the following private function:

_removeSignalFor( Key ) simply deletes a signal by nullifying its listeners, and then removing references to it in our dictionaries, all to be safe for memory management purposes.

Next add the following private function:

Every signal has the array property listeners, and _annulEmpty( Signal ) simple deletes any signal which has nothing left listening to it, by querying the count property of listeners. As you can see, it calls _removeSignalFor( Key ).

Next add the following private function:

As you can see, it calls _annulEmpty( Signal ). Let's go through what happens here:

  1. If we are trying to remove a non-existent listener, just block the call;
  2. Otherwise stop listening by just calling Signal's removeListener() function;
  3. Finally, assess whether this was the last listener this signal had, and if so delete it.

We are now in a position to remove listeners.

Add the following code to removeListenerFor( Key ):

This simply calls the private function _removeListenerFor( Key ). And as in the process of creating the functionality to remove listeners we created the private function _removeSignalFor( Key ), we are now also in a position to remove signals.

Add the following code to removeSignalFor( Key ):

This simply checks if we have a given signal, and if so deletes it. So that's listeners added and removed, and signals added. All that's left to do is dispatch a signal.

Add the following code to dispatchSignalFor( Key ):

Signals creator Tuomas Artman says only that => is a "special operator" which can be used to fire signals. I have not been able to find any more information on this special operator, more information would be gratefully received if you have any.

With those functions added the code is complete. Here is the full class without line breaks: