The observer pattern is another behavioral pattern that is often used in networked system where a subject (the server) will notify some client. The iOS makes large use of this pattern through NSNotificationCenter
object.
The observer pattern creates dependence between a subject and observer so that the observer is notified in order to update their state each time the subject is modified.
This composition means that observer does not need to ask the current state of the subject. They only need to register to its notifications.
This pattern can be used when:
This pattern is composed of the following participants:
Subject
: This defines the methods needed to add, remove, and notify observers.ConcreteSubject
: This implements the Subject
methods. It sends a notification when its state is modified.Observer
: This is a common interface having an update()
method, which will be invoked by the subject when the observer needs to be notified about the modification of the subject.ConcreteObserver1
and ConcreteObserver2
: This implements the update()
method.The ConcreteSubject
class notifies the observers when its internal state is modified. When a concrete observer receives this notification, it is updated consequently. To complete the update, it can invoke some subject methods that give access to its state.
You are working on a new website where you want to allow internet users to communicate each other through a chat system. Your first job will be to provide a room, the entry point of all Internet users. Each time a new user joins the room, every user is notified.
The observer pattern is fully appropriated to implement the code, which solves this problem.
Open the ObserverPattern
Xcode project to see the current structure of our code:
We will retrieve the Subject
folder and Observers
folder, where we will find the participants of our pattern. The Helpers
folder contains a class that we will use later when sending a message.
The Extension
folder contains an array extension that is required to make it possible for us to remove a particular object from the collection of users managed by the subject.
Lastly, we find the main.swift
file used to simulate interactions.
So, let us begin our code by defining our observer in the UserProtocol.Swift
file:
protocol UserProtocol { func update(object:AnyObject) }
We simply define an update method with an object as an argument. The implementation of the UserProtocol
will be like this:
class User: UserProtocol{ let name: String! init(name: String) { self.name = name } func update(object:AnyObject) { let info = object as! Info print("(self.name) notified that (info.message) have status (info.status) on (info.date.description)") } }
We pass a name in the constructor of the User
object.
Then, in the update
method, we prepare a message that will be displayed on the console. We downcast our object of type AnyObject
to an Info
object; this object is a helper. You will find its code in the Helper
folder in the Info.swift
file:
class Info { var date = NSDate() var message:String! var status:InfoStatus! init(msg: String, status:InfoStatus) { self.message = msg self.status = status } }
The Info
object contains three values: a date, message, and status.
The date is the current date and is defined when the Info
object is initialized. The message is a string received in argument during initialization of the info object and the status is an enumeration passed in an argument during the initialization of the object and it can have the following value:
enum InfoStatus { case Join case Leave }
Now, we only have to define our Subject protocol, and implement it in our concrete Subject. The Subject represents the object that need to be observed.
Our Subject definition is available in the RoomProtocol.swift
file in the Subject
folder:
protocol RoomProtocol { func addObserver(user: User) func removeObserver(user: User) func notifyObserver(object: AnyObject) }
These three methods are the minimum necessary to any subject in an observer pattern.
The addObserver
function lets you register an observer in the collection of observers managed by the subject.
The removeObserver
method is used to remove an observer from the collection managed by the subject.
Last, the notifyObserver
method is used to notify all our observers.
The implementation will be found in the Room.swift
file, as shown:
class Room: RoomProtocol { private var users = [User]() func addObserver(user: User) { users.append(user) let info = Info(msg: "(user.name)", status: .Join) notifyObserver(info) } func removeObserver(user: User) { users.removeObject(user) let info = Info(msg: "(user.name)", status: .Leave) notifyObserver(info) } func notifyObserver(object: AnyObject){ for u in users { u.update(object) } } }
Here, you retrieve the three methods, in the two first one; you see a call to the notifyObserver
method.
All the users will be notified each time the addObserver
or removeObserver
method is called because the addObserver
method is called when a new user joins the room and we display the Join
status in the Info
message. With the same principle, we display the Leave
status when the removeObserver
method is called.
The notifiyObserver
method receives an object of type AnyObject
as an argument that will be propagated over the update method of each user object available in the collection managed by the Room
method.
Time for us to now write our demo code, open the main.swift
file, and write the code.
First, we initialize our Room
method and four internet users:
let room = Room() let user1 = User(name:"Julien") let user2 = User(name:"Alain") let user3 = User(name:"Helmi") let user4 = User(name:"Raphael")
Then, we register each user to the room:
room.addObserver(user1) room.addObserver(user2) room.addObserver(user3) room.addObserver(user4)
Now, we remove user2
, user3
, and user1
in this order:
room.removeObserver(user2) room.removeObserver(user3) room.removeObserver(user1)
To complete our sample, we register user2
once more:
room.addObserver(user2)
Let us now build and run the project. You will get the following result:
Here, we see all the notifications. The first line corresponds to the first addObserver
called. The next two lines correspond to the second addObserver
called, and so on.