Greetings, traveler!
We continue to discuss Design Patterns. It is the turn of another Behavioral Design Pattern, the Mediator.
At first glance it feels similar to Observer. Or even to a simple service object. The difference only becomes clear when the system starts growing and objects begin talking to each other in too many directions.
Mediator exists for one reason: to stop objects from knowing too much about each other.
Let’s build a real example and see when it actually helps.
The problem: too many direct connections
Imagine a simple settings screen with three components:
- WiFiSwitch
- CellularSwitch
- AirplaneModeSwitch
There is business logic behind them:
- Enabling Airplane Mode must disable WiFi and Cellular.
- Enabling WiFi while Airplane Mode is on must disable Airplane Mode.
- Disabling Cellular might trigger a warning label somewhere else.
If each component talks directly to the others, the dependencies quickly become messy.
final class WiFiSwitch {
var airplaneMode: AirplaneModeSwitch?
func turnOn() {
airplaneMode?.turnOff()
}
}Now multiply this across three or four components. Each one keeps references to others. Logic spreads everywhere. Changing one rule means touching multiple classes.
This is where Mediator makes sense.
The idea behind mediator
Instead of components communicating directly, they send events to a central object. That object decides what should happen next.
Components no longer know about each other. They only know about the mediator.
Let’s model it properly.
Defining the mediator protocol
protocol SettingsMediator: AnyObject {
func notify(sender: AnyObject, event: SettingsEvent)
}
enum SettingsEvent {
case wifiOn
case wifiOff
case cellularOn
case cellularOff
case airplaneOn
case airplaneOff
}The mediator does not expose specific methods like enableWiFi or disableCellular. It receives events and coordinates behavior.
That keeps components dumb. The mediator owns the logic.
Building the concrete mediator
final class DefaultSettingsMediator: SettingsMediator {
private var wifi: WiFiSwitch?
private var cellular: CellularSwitch?
private var airplane: AirplaneModeSwitch?
func register(
wifi: WiFiSwitch,
cellular: CellularSwitch,
airplane: AirplaneModeSwitch
) {
self.wifi = wifi
self.cellular = cellular
self.airplane = airplane
}
func notify(sender: AnyObject, event: SettingsEvent) {
switch event {
case .airplaneOn:
wifi?.set(enabled: false)
cellular?.set(enabled: false)
case .wifiOn:
airplane?.set(enabled: false)
case .cellularOn:
airplane?.set(enabled: false)
default:
break
}
}
}All coordination logic lives here. If the rules change, you update one place.
Updating the components
Components now depend only on the mediator.
final class WiFiSwitch {
private weak var mediator: SettingsMediator?
private(set) var isEnabled = false
init(mediator: SettingsMediator) {
self.mediator = mediator
}
func toggle() {
isEnabled.toggle()
if isEnabled {
mediator?.notify(sender: self, event: .wifiOn)
} else {
mediator?.notify(sender: self, event: .wifiOff)
}
}
func set(enabled: Bool) {
isEnabled = enabled
}
}The switch does not know about Cellular or Airplane Mode anymore. It simply reports state changes.
The same structure applies to other switches.
Wiring everything together
let mediator = DefaultSettingsMediator()
let wifi = WiFiSwitch(mediator: mediator)
let cellular = CellularSwitch(mediator: mediator)
let airplane = AirplaneModeSwitch(mediator: mediator)
mediator.register(
wifi: wifi,
cellular: cellular,
airplane: airplane
)Now interactions flow through one center.
Why this is not observer
Observer broadcasts changes to subscribers. Mediator coordinates behavior between peers.
In Observer:
- One object publishes
- Many observe
In Mediator:
- Multiple objects interact
- One object decides how they affect each other
If your logic is just “tell everyone something changed,” you probably need Observer.
If your logic sounds like “when A does this, B and C must react in a specific way,” that is mediator territory.
When mediator is useful in iOS projects
You will see this pattern in:
- Complex forms where fields depend on each other
- Onboarding flows with conditional steps
- Coordinators managing screen interactions
- Feature modules that must stay isolated
In SwiftUI, this often appears as a ViewModel that coordinates smaller sub-view models. You may not call it Mediator, but the structure is the same.
When not to use it
If there are only two objects interacting, mediator adds ceremony.
If the interaction is simple and stable, direct references are fine.
Mediator helps when rules change often and connections multiply. It is not a default solution, but a response to growing complexity.
Conclusion
Mediator reorganizes dependencies. Instead of many objects depending on each other, they depend on one coordinator. That shift alone can make a system easier to reason about.
The moment you notice objects passing messages in multiple directions, stop and sketch the dependency graph. If it looks like spaghetti, a mediator may be the simplest way to untangle it.
And the best part is that the pattern in Swift is lightweight. Just protocols and discipline.
Now, let’s move on to the next design pattern, the Memento pattern.
Check out other posts in the Design Patterns series:
- Visitor Design Pattern in Swift
- Template Method Design Pattern in Swift
- Strategy Design Pattern in Swift
- State Design Pattern in Swift
- Observer Design Pattern in Swift
- Memento Design Pattern in Swift
- Iterator Design Pattern in Swift
- Command Design Pattern in Swift
- Chain of Responsibility Design Pattern in Swift
- Proxy Design Pattern in Swift
- FlyWeight Design Pattern in Swift
- Facade Design Pattern in Swift
- Decorator Design Pattern in Swift
- Composite Design Pattern in Swift
- Bridge Design Pattern in Swift
- Adapter Design Pattern in Swift
- Singleton Design Pattern in Swift
- Prototype Design Pattern in Swift
- Builder Design Pattern in Swift
- Abstract Factory Design Pattern in Swift
- Factory Method Design Pattern in Swift
- Design Patterns: Basics
