Observer – Design Patterns in Swift

Introduction

The Observer pattern addresses three problems. Firstly, a one-to-many dependency between objects should be defined without making the objects tightly coupled. Secondly, it should be ensured that when one object changes state, an open-ended number of dependent objects are updated automatically. Finally, it should be possible that one object can notify an open-ended number of other objects.

Observer Pattern – UML

Use Observer Pattern in Swift

The Subject owns some important state and notifies observers when the state changes.

class Subject {

    var state: Int = { return Int(arc4random_uniform(10)) }()

    private lazy var observers = [Observer]()

    /// The subscription management methods.
    func attach(_ observer: Observer) {
        print("Subject: Attached an observer.\n")
        observers.append(observer)
    }

    func detach(_ observer: Observer) {
        if let idx = observers.firstIndex(where: { $0 === observer }) {
            observers.remove(at: idx)
            print("Subject: Detached an observer.\n")
        }
    }

    /// Trigger an update in each subscriber.
    func notify() {
        print("Subject: Notifying observers...\n")
        observers.forEach({ $0.update(subject: self)})
    }

    func someBusinessLogic() {
        print("\nSubject: I'm doing something important.\n")
        state = Int(arc4random_uniform(10))
        print("Subject: My state has just changed to: \(state)\n")
        notify()
    }
}

For the sake of simplicity, the Subject’s state, essential to all subscribers, is stored in this variable. In real life, the list of subscribers can be stored more comprehensively (categorized by event type, etc.).

Usually, the subscription logic is only a fraction of what a Subject can really do. Subjects commonly hold some important business logic, that triggers a notification method whenever something important is about to happen (or after it).

The Observer protocol declares the update method, used by subjects.

protocol Observer: class {

    func update(subject: Subject)
}

Concrete Observers react to the updates issued by the Subject they had been attached to.

class ConcreteObserverA: Observer {

    func update(subject: Subject) {

        if subject.state < 3 {
            print("ConcreteObserverA: Reacted to the event.\n")
        }
    }
}

class ConcreteObserverB: Observer {

    func update(subject: Subject) {

        if subject.state >= 3 {
            print("ConcreteObserverB: Reacted to the event.\n")
        }
    }
}

Let’s see how it all works together.

class ObserverConceptual: XCTestCase {

    func testObserverConceptual() {

        let subject = Subject()

        let observer1 = ConcreteObserverA()
        let observer2 = ConcreteObserverB()

        subject.attach(observer1)
        subject.attach(observer2)

        subject.someBusinessLogic()
        subject.someBusinessLogic()
        subject.detach(observer2)
        subject.someBusinessLogic()
    }
}

Conclusion

In summary, this pattern can be recognized by subscription methods, that store objects in a list and by calls to the update method issued to objects in that list. The Observer pattern is pretty common in Swift code, especially in the GUI components. It provides a way to react to events happening in other objects without coupling to their classes.

Leave a Reply

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