- Published on
Swift Delegate, Notification Center, and KVO
- Authors
- Name
- Tien Tran
- Mobile Developer at Use Inc.
Delegate
delegate
is the most common type of loose connection between entities is Cocoa, but nonetheless, this approach can be used in other languages and platforms as this is basically a delegation design pattern.
The idea is simple - instead of interacting with an object through its real API we can instead omit the details of which exact type we talk to and instead use protocol
with a subset of methods that we need. We define the protocol ourselves and only list those methods we’re going to call at our opponent. A real object then would need to implement this protocol, and probably other methods - but we won’t know about it - and that’s good! Loose coupling, remember?
Let’s see an example. Suppose we have a Pizzeria
, with a man who makes the pizza (pizzaiolo), and a customer who orders the pizza. The pizzaiolo
should not know all the details about who exactly is the customer (could it be Queen Elizabeth, or Snoop Dogg, or just a dog…), the only thing that matters is the customer’s ability to take the pizza when it is ready. This is how we would implement this problem with delegate
in Swift:
// The protocol the Pizzaiolo is using for "calling back" to customer
protocol PizzaTaker {
func take(pizza: Pizza)
}
class Pizzaiolo {
// We store a reference to a PizzaTaker rather than to a Customer
var pizzaTaker: PizzaTaker?
// The method a customer can call to ask Pizzaiolo to make a pizza
func makePizza() {
...
// As soon as the pizza is ready the `onPizzaIsReady` function is called
}
private func onPizzaIsReady(pizza: Pizza) {
// Pizzaiolo provides the pizza to a PizzaTaker
pizzaTaker?.take(pizza: pizza)
}
}
// Class Customer implements the protocol PizzaTaker - the only requirement
// which allows him to get a pizza from Pizzaiolo
class Customer: PizzaTaker {
// Some details related to Customer class which Pizzaiolo should not know about
var name: String
var dateOfBirth: Date
// Implementation of the PizzaTaker protocol
func take(pizza: Pizza) {
// yummy!
}
}
Advantages delegate
- Loose coupling. Indeed, with delegate pattern, we have interacting parties decoupled from each other without exposure of who exactly they are. We can easily implement the protocol PizzaTaker in other class Dumpster and make all fresh pizzas end up in a trash can instead of being eaten, without Pizzaiolo even knowing something has changed.
- IDE (such as Xcode) is able to check the connection for correctness with static analysis. This is a great advantage because connections between objects can break when you refactor things, and IDE will point you out the problem even before you try to build the project.
- Ability to get a non-void result from calling the delegate. Unlike many other callback techniques, with the delegate, you can not only notify but also ask for data. In Cocoa, you can often see DataSource protocols, which stand exactly for this purpose.
- Speed. Since calling a method on a delegate is nothing more than the direct call of a function, with delegate you can achieve absolutely best performance among other callback techniques, which have to spend time on more sophisticated delivery of the call.
NotificationCenter
NotificationCenter
(or NSNotificationCenter
for Objective-C) is a class in Cocoa which provides the publish – subscribe
functionality, and as you can guess from its name, it deals with Notifications. The notifications it sends around are objects of Notification
class, which can carry an optional payload, but more often are used just as notices. The NotificationCenter
itself is a data bus, it doesn’t send notifications on its own, only when someone askes it to send one. The main feature of this pattern is that the sender and recipients (there can be many) do not talk directly, like with delegate pattern
. Instead, they both talk to NotificationCenter
- the sender calls method postNotification on the NotificationCenter to send a notification, while recipients opt-in for receiving the notifications by calling addObserver
on NotificationCenter. They can later opt-out with removeObserver
. Important note though is that NotificationCenter does not store notifications for future subscribers - only present
subscribers receive the notifications.
The NotificationCenter
also provides a global access point defaultCenter
, however, this class is not implemented as a pure Singleton
- you can create your own instances of NotificationCenter (and you probably should).
Implementation of the same case with Pizzeria
:
// First step is to declare new notification type - to be identified by sender and recipients
extension NSNotification.Name {
static let PizzaReadiness = NSNotification.Name(rawValue: "pizza_is_ready")
}
class Pizzaiolo {
func makePizza() {
...
}
private func onPizzaIsReady(pizza: Pizza) {
// Pizzaiolo notifies all interested parties that the pizza is ready:
NotificationCenter.default.post(name: NSNotification.Name.PizzaReadiness, object: self, userInfo: ["pizza_object" : pizza])
}
}
class Customer {
// If a customer wants to get a pizza he needs to register as an observer at NotificationCenter
func startListeningWhenPizzaIsReady() {
// A customer subscribes for all notifications of type NSNotification.Name.PizzaReadiness
NotificationCenter.default.addObserver(self, selector: #selector(pizzaIsReady(notification:)), name: NSNotification.Name.PizzaReadiness, object: nil)
}
// The customer should opt-out of notifications when he's not interested in them anymore
func stopListeningWhenPizzaIsReady() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.PizzaReadiness, object: nil)
}
dynamic func pizzaIsReady(notification: Notification) {
if let pizza = notification.userInfo?["pizza_object"] as? Pizza {
// Got the pizza!
}
}
}
Advantages NotificationCenter
- Multiple recipients
NotificationCenter
transparently delivers a Notification toall subscribers
, could there be one, or thousand, or none - this class will take care of the delivery for you. What’s also notable - thesender
cannot know how many subscribers he has. Not to say it’s good or bad, it’s just how this tool is designed. - Loose coupling. When using
NotificationCenter
the only thing that couplessender
andreceivers
together is the name of the notification they use for their communication. If you include a payload to the notification, both parties would need to have symmetric boxing and unboxing of the data. - Global access point. When you don’t need (or just don’t care about) the dependency injections in your code, the singleton-style method
defaultCenter
allows you to connect two random objects together with ease.
Key Value Observing
KVO
is a traditional observer
pattern built-in any NSObject
out of the box. With KVO
observers can be notified of any changes of a @property
values. It leverages Objective-C
runtime for automated notifications dispatch, and because of that for Swift
class, you’d need to opt into Objective-C dynamism
by inheriting NSObject
and marking the var
you’re going to observe with modifier dynamic
. The observers should also be NSObject descendants because this is enforced by the KVO
API.
Sample code for Key-Value Observing
a property value
of the class ObservedClass
:
class ObservedClass : NSObject {
@objc dynamic var value: CGFloat = 0
}
class Observer {
var kvoToken: NSKeyValueObservation?
func observe(object: ObservedClass) {
kvoToken = object.observe(\.value, options: .new) { (object, change) in
guard let value = change.new else { return }
print("New value is: \(value)")
}
}
deinit {
kvoToken?.invalidate()
}
}
Advantages Key-Value Observing
- Observation pattern in a few lines of code. Normally you would need to implement it yourself, but in
Cocoa
you have this feature coming standard for everyNSObject
. - Multiple observers - there is no limitation on the number of subscribers.
- There is no need to change the source code of the class under observation
- Because of the aforementioned you can observe objects of any class (including those from system frameworks, to which sources we don’t have access thus cannot modify)
- Very low coupling connascence of name - the observed party doesn’t even know it’s being observed, the observing party’s knowledge is bounded by the name of the
@property
- Notification can be configured to deliver not only the most recent value of the observed
@property
but also the previous value.