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.
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!