Published on

Swift Delegate, Notification Center, and KVO

Authors
  • avatar
    Name
    Tien Tran
    Twitter
  • 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 to all subscribers, could there be one, or thousand, or none - this class will take care of the delivery for you. What’s also notable - the sender 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 couples sender and receivers 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 every NSObject.
  • 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.