Observer Pattern
The observer pattern let’s an object observe the changes notified by another object.
- Loosely coupled design between interacting objects.
- 1:N communication pattern - Notification/Observer pattern
-
New observers can be added without impacting other observer or value/subject sent.
Part of Combine* - Swift 5.1 makes it easy to implement the observer pattern using @Published properties.
Design overview
Consists of three parts:
- Subscriber: Observer object that subscribes and receives updates.
- Publisher: Observable object. Sends updated/notifications.
- Value: Underlying object that has changed.
When should you use this pattern?
Use this pattern when an object wants to be notified of changes made on another object.
Use Cases
Often used with MVC:
Controller observes model changes.
Model is able to communicate changes back to controller, without needing to know anything about the controller.
Be Careful with
- For simple model/properties that never change, this pattern is an overkill.
- Do not use it with a singleton and multi cases.
- Hard to traced and debug issue.
- Costly on thread used as when a notification is done, all observer handlers are called on the same thread. Watchout for calling handlers on the background thread when they are used for updating the UI.
- Before using this pattern, define what you expect to change and when (under what conditions).
- Cannot use @Published on let properties as it cannot be changed.
- Cannot be used on structs - Only on classes.
- Only if a package already has it implemented, then use it. Else it is strongly advised not to use this pattern.
Code Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Combine
public class User {
@Published var name: String
public init(name:String) {
self.name = name
}
}
let user = User(name: "Gustavo")
//This returns an object of type published, string.publisher. This object is what can be listened to for updates.
// Accessed the publisher for broadcasting changes to the user's name at user.$name
let publisher = user.$name
var subscriber: AnyCancellable? = publisher.sink() { // by calling sink, you create a subscriber on publisher.
print("User's name is \($0)") // closure gets called, when initial value changes.
}
1
2
3
4
5
6
7
8
9
user.name = "Fernando"
user.name = "Vicky"
subscriber = nil
user.name = "Gustavo has left the building"
// prints
// User's name is Gustavo
// User's name is Fernando
// User's name is Vicky