Coraline Ada Ehmke

When Objects Talk: Notifications in Swift

Coraline Ada Ehmke | September 16, 2017

I've been working on my first desktop application for macOS using Swift, and I'm really enjoying the platform and the language. Unfortunately, for whatever reason, Swift for desktop apps isn't talked about very much. This means that much of the material available online about Swift in the form of tutorials, sample code, Stack Overflow answers, and general Google results are tailored for iOS development (which uses the same core but has vastly different libraries).

With this in mind, I decided to write a series of blog posts to share some of the tips and techniques that I am discovering through my development process. To start off, let's talk about notifications.

No, not that sort of notification. I'm talking about the notification infrastructure that allows objects in Swift to share information with each other through message passing.

A Basic Setup

Say we have two Swift classes, a ViewController and a MyObject model. The ViewController is responsible for creating an object and needs to update the UI as the state of this object changes. Our hypothetical MyObject looks something like this:


class MyObject {

  var id: String
  var status: Status
  var data: [String: AnyObject]
  var service = MyBackEndServiceController()

  enum Status: String {
    case Initialized
    case Lookup
    case Retrieved
    case Populated
    case Error
  }

  init(id: String, status: Status) {
    self.id = id
    self.status = status
  }

  /// Hit the backend service to get data
  func populateFromAPI() {
    self.status = Status.Lookup

    /// Call the service controller
    if let data = service.getDataFromServer(id: self.id) {
      self.status = Status.Retrieved
    } else {
      self.status = Status.Error
    }

    do {
      let _ = try parseJSON()
      self.status = Status.Populated
    } catch {
      self.status = Status.Error
    }

  }

  fileprivate func parseJSON() throws -> Bool {
    /// Do something with that data!
    return true
  }

}

We want the view controller to know when the state of the object has changed, and we don't want the overhead of something like polling. This is where notifications prove very useful.

To start with, we add a notifyOnStatusChange function to MyObject:


fileprivate func notifyOnStatusChange() {
  NotificationCenter.default.post(name: Notification.Name(rawValue: "com.example.myobject"), object: self, userInfo: ["status": self.status.rawValue])
}

This code will throw a Notification object into NotificationCenter.default so that any other class can subscribe to its messages. Let's add calls to this function to our code:


func populateFromAPI() {
  self.status = Status.Lookup

  /// Call the service controller
  if let data = service.getDataFromServer(id: self.id) {
    self.status = Status.Retrieved
    notifyOnStatusChange()
  } else {
    self.status = Status.Error
    notifyOnStatusChange()
  }

  do {
    let _ = try parseJSON()
    self.status = Status.Populated
    notifyOnStatusChange()
  } catch {
    self.status = Status.Error
    notifyOnStatusChange()
  }
}

So what does the subscription implementation looks like? We simply add this to our ViewController:


func createAThing() {
  var thing = MyObject(id: "1", status: MyObject.Status.Initialized)
  NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "com.example.myobject), object: thing, queue: nil, using: updateUIWithStatus)
}

In this example, we need to define a updateUIWithStatus function in our ViewController:


func updateUIWithStatus(notification: Notification) {
  let newStatus = notification.userInfo!["status"]
  self.statusField.stringValue = newStatus
}

That's it! Now the ViewController will get a message any time the notifyOnStatusChange function in an instance of MyObject is called, and it can do what it wants with this message.

Notifications are great whenever you need to track changes in an object. Another good use of notifications is for asynchronous operations like pulling data from an API. In this case notifications are a good substitute for a promise, and best of all they're baked into the Foundation framework, so you don't need to bring in a Cocoapod to use them.

So give notifications a try, and happy hacking!