This tutorial is the second installment in an AppCoda series on design patterns started last week. There are 23 classic software development design patterns probably first identified, collected, and explained all in one place by the “Gang of Four” (“GoF”), Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in their seminal book, “Design Patterns: Elements of Reusable Object-Oriented Software.”. Today, we’ll focus on two of these patterns, “observer” and “memento,” which fall into what the GoF calls the “behavioral” category.
Software development is an endeavor into modeling real world scenarios in the hopes of creating tools to enhance the human experience in such scenarios. Tools for managing finances, e.g., banking apps and shopping aids like Amazon or eBay’s iOS apps, definitely make life much simpler than it was for consumers just ten years ago. Think of how far we’ve come. While software apps have generally gotten more powerful and simpler to use for consumers, development of said apps has gotten much more complex for developers.
So developers have created an arsenal of best practices to manage complexity, like object-oriented programming, protocol-oriented programming, value semantics, local reasoning, breaking large pieces of code into smaller ones with well-defined interfaces (like with Swift extensions), syntactic sugar, to name some of the most popular. One of the most important best practices that I didn’t mention, but that merits much attention, is the use of design patterns.
Design Patterns
Design patterns are an extremely important tool with which developers can manage complexity. It’s best to conceptualize them as generally templated techniques, each tailored to solving a corresponding, recurring, and readily identifiable problem. Look at them as a list of best practices you would use for coding scenarios that you see over and over again, like how to create objects from a related family of objects without having to understand all the gory implementation details of that family. The whole point of design patterns is that they apply to commonly occurring scenarios. They’re reusable because they’re generalized. A specific example should help.
Design patterns are not specific to some use case like iterating over a Swift array of 11 integers (Int
). For example, the GoF defined the iterator pattern to provide a common interface for traversing through all items in some collection without knowing the intricacies (i.e., type) of the collection. A design pattern is not programming language code. It is a set of guidelines or rule of thumb for solving a common software scenario.
Remember that I discussed the “Model-View-ViewModel” or “MVVM” design pattern here on AppCoda — and of course the very well-known “Model-View-Controller” or “MVC” design pattern, long favored by Apple and many iOS developers.
These two patterns are generally applied to entire applications. MVVM and MVC are architectural design patterns and are meant to separate the user interface (UI) from the app’s data and from code for presentation logic, and to separate the app’s data from core data processing and/or business logic. The GoF design patterns are more specific in nature, meant to solve more distinct problems inside an application’s code base. You may use three or seven or even twelve GoF design patterns in one app. Remember my iterator example. Delegation is another great example of a design pattern, though not specifically on the GoF’s list of 23.
While the GoF book has taken on biblical connotations for many developers, it is not without its detractors. We’ll talk about that in the conclusion to this article.
Design pattern categories
The GoF organized their 23 design patterns into 3 categories, “creational,” “structural,” and “behavioral.” This tutorial discusses two patterns in the behavioral category. This pattern’s purpose is imposing safe, common sense, and consistent etiquette, form, and best practices for behaviour onto classes and structs (actors). We want good, consistent, and predictable behavior from all actors inside an entire application. We want good behavior both within actors themselves and between different actors that interact/communicate. Evaluation of actors’ behavior should be considered before and at compile time, which I sometimes call “design time,” and at runtime, when we’ll have lots of instances of classes and structs all doing their own thing and constantly interacting/communicating. Since communication between instances leads to so much software complexity, having rules for consistent, efficient, and safe communications is of paramount importance, but that concept should not in any way diminish the need for good design practices when building each individual actor. Since we’re so focused on behavior, we must remember to use consistent patterns when assigning responsibilities to actors and when assigning responsibilities to combinations of actors.
Before I wax too far theoretical, let me provide tangible examples to assure you that I’ll be demonstrating theory with in-depth practice as realized in Swift code. In this tutorial, we’ll see how to consistently assign responsibility for maintaining an actor’s state. We’ll also see how to consistently assign responsibility for one actor, the subject, to send notifications to many actor-observers, and conversely, how to consistently assign responsibility to the observers for registering with the subject to get notifications.
The whole notion of consistency should be self-evident to you when discussing design patterns. Keep in mind a theme that was highlighted in last week’s post, a theme that will occur over and over as we discuss more and more design patterns: hiding complexity (encapsulation). It is one the highest goals of smart developers. For example, object-oriented (OOP) classes can provide very complex, sophisticated, and powerful functionality without requiring the developer to know anything about the internal workings of those classes. In the same vein, Swift protocol-oriented programming is an extremely important yet relatively new technique for controlling complexity. Managing complexity is a developer’s greatest burden, but here we are talking about taming that beast!
A note on this tutorial’s prose and definitions
For this tutorial, I’ve decided to concentrate my explanatory prose as inline commentary in my sample apps’ code. While I will provide brief explanations of today’s design pattern concepts, I very much want you to look at my code, read the comments, and fully internalize the techniques I’m sharing with you. After all, if you can’t write the code, and can only talk about the code, you’re not going to make it through very many job interviews — and you’re not really a hardcore developer.
You probably noticed in my definition of the behavioral design pattern that I’m following Apple’s documentation convention:
An instance of a class is traditionally known as an object. However, Swift structures and classes are much closer in functionality than in other languages, and much of this chapter describes functionality that applies to instances of either a class or a structure type. Because of this, the more general term instance is used.
I’ve also taken the liberty of referring to classes and structs as “actors” at design time.
The observer design pattern
The observer pattern is probably something you’ve all experienced while using Apple mobile devices, but hopefully you’ve also noticed it while coding your iOS apps. Every time it rains where I live, I and a whole lot of other people around here get push notifications which pop up on our iPhones — lock screen or normal screen — whenever it rains. Here’s what they look like:
One source, Apple, is sending out — broadcasting — notifications to many thousands of iPhones, on behalf of the National Weather Service, warning people in my area of the possibility of flooding. In more concrete terms, like at the level of an iOS app, one instance, the subject, notifies (many) other instances, the observers, of changes to the state of the subject. The instances participating in this broadcast type communication don’t need to know about one another. This is a great example of loose coupling.
The subject instance, usually a single critical resource, broadcasts notifications about a change in its state to many observer instances that depend on that resource. Interested observers must subscribe to get notifications.
Thankfully, iOS has a built-in and well-known feature for enabling the observer pattern: NotificationCenter, which I’ll let you study up on yourself here.
Use case for observer design pattern app
My observer example project, available on GitHub, showcases how this broadcast type communication works.
I know this is not quite how the iOS Human Interface Guidelines advise you to do things, but bear with me as I needed an example of a single critical resource. Suppose we were to take a proactive approach and have a single instance of a subject responsible for monitoring network connectivity/reachability. This is the broadcaster, and to implement one, you just need to have an actor adopt my ObservedProtocol
.
Suppose there are multiple instances of observers, like an image downloader class, a login instance that verifies user credentials via a REST API, and an in-app browser that all subscribe to the subject to get notified of network connection status. To implement these, you could take pretty much any class and make it a descendent of my “abstract” Observer
class, which adopts the ObserverProtocol
. (I’ll explain later why I limited my example observer code to a class.)
To implement observers for the purposes of my example app, I created the NetworkConnectionHandler
class. When instances of this concrete class get notifications of NetworkConnectionStatus.connected
, they turn a UIView
instance green; when they get notifications of NetworkConnectionStatus.disconnected
, they turn a UIView
instance red.
Here’s my sample app code compiled, installed, and running on an iPhone 8 Plus:
Here’s the Xcode console output corresponding to the previous video:
Sample code for observer design pattern app
To see the heavily commented code I just described in the previous section, please look at file Observable.swift
in my sample project:
import Foundation import UIKit // Make notification names consistent and avoid stringly-typing. // Try to use constants instead of strings or numbers. extension Notification.Name { static let networkConnection = Notification.Name("networkConnection") static let batteryStatus = Notification.Name("batteryStatus") static let locationChange = Notification.Name("locationChange") } // Various network connection states -- made consistent. enum NetworkConnectionStatus: String { case connected case disconnected case connecting case disconnecting case error } // The key to the notification's "userInfo" dictionary. enum StatusKey: String { case networkStatusKey } // This protocol forms the basic design for OBSERVERS, // entities whose operation CRTICIALLY depends // on the status of some other, usually single, entity. // Adopters of this protocol SUBSCRIBE to and RECEIVE // notifications about that critical entity/resource. protocol ObserverProtocol { var statusValue: String { get set } var statusKey: String { get } var notificationOfInterest: Notification.Name { get } func subscribe() func unsubscribe() func handleNotification() } // This template class abstracts out all the details // necessary for an entity to SUBSCRIBE to and RECEIVE // notifications about a critical entity/resource. // It provides a "hook" (handleNotification()) in which // subclasses of this base class can pretty much do whatever // they need to do when specific notifications are received. // This is basically an "abstract" class, not detectable // at compile time, but I felt this was an exceptional case. class Observer: ObserverProtocol { // This specific state reported by "notificationOfInterest." // I use String for maximum portability. Yeah, yeah... // stringly-typed... var statusValue: String // The key to the notification's "userInfo" dictionary, with // which we can read the specific state and store in "statusValue." // I use String for maximum portability. Yeah, yeah... // stringly-typed... let statusKey: String // The name of the notification this class has registered // to receive whenever messages are broadcast. let notificationOfInterest: Notification.Name // Initializer which registers/subscribes/listens for a specific // notification and then watches for a specific state as reported // by notifications when received. init(statusKey: StatusKey, notification: Notification.Name) { self.statusValue = "N/A" self.statusKey = statusKey.rawValue self.notificationOfInterest = notification subscribe() } // We're registering self (this) with NotificationCenter to receive // all notifications with the name stored in "notificationOfInterest." // Whenever one of those notifications is received, the // "receiveNotification(_:)" method is called. func subscribe() { NotificationCenter.default.addObserver(self, selector: #selector(receiveNotification(_:)), name: notificationOfInterest, object: nil) } // It's a good idea to un-register from notifications when we no // longer need to listen, but this is more of a historic curiosity // as, since iOS 9.0, the OS does some type of cleanup. func unsubscribe() { NotificationCenter.default.removeObserver(self, name: notificationOfInterest, object: nil) } // Called whenever a message labelled "notificationOfInterest" // is received. This is our chance to do something when the // state of that critical resource we're observing changes. // This method "must have one and only one argument (an instance // of NSNotification)." @objc func receiveNotification(_ notification: Notification) { if let userInfo = notification.userInfo, let status = userInfo[statusKey] as? String { statusValue = status handleNotification() print("Notification \(notification.name) received; status: \(status)") } } // end func receiveNotification // YOU MUST OVERRIDE THIS METHOD; YOU MUST SUBCLASS THIS CLASS. // I've MacGyvered this class into being "abstract" so you // can subclass and specialize as much as you want and not // have to worry about NotificationCenter details. func handleNotification() { fatalError("ERROR: You must override the [handleNotification] method.") } // Be kind and stop tapping a resource (NotificationCenter) // when we don't need to anymore. deinit { print("Observer unsubscribing from notifications.") unsubscribe() } } // end class Observer // An example of an observer, usually one of several // (many?) that are all listening for notifications // from some usually single critical resource. Notice that // it's brief and can serve as a model for creating // handlers for all sorts of notifications. class NetworkConnectionHandler: Observer { var view: UIView // As long as you call "super.init" with valid // NotificationCenter-compatible values, you can // create whatever type of initializer you want. init(view: UIView) { self.view = view super.init(statusKey: .networkStatusKey, notification: .networkConnection) } // YOU MUST OVERRIDE THIS METHOD, but that // gives you the chance to handle notifications // in whatever way you deem fit. override func handleNotification() { if statusValue == NetworkConnectionStatus.connected.rawValue { view.backgroundColor = UIColor.green } else { view.backgroundColor = UIColor.red } } // end func handleNotification() } // end class NetworkConnectionHandler // An template for a subject, usually a single // critical resource, that broadcasts notifications // about a change in its state to many // subscribers that depend on that resource. protocol ObservedProtocol { var statusKey: StatusKey { get } var notification: Notification.Name { get } func notifyObservers(about changeTo: String) -> Void } // When an adopter of this ObservedProtocol // changes status, it notifies ALL subsribed // observers. It BROADCASTS to ALL SUBSCRIBERS. extension ObservedProtocol { func notifyObservers(about changeTo: String) -> Void { NotificationCenter.default.post(name: notification, object: self, userInfo: [statusKey.rawValue : changeTo]) } } // end extension ObservedProtocol
I had intended to put most of the observer’s notification handling logic into an extension of ObserverProtocol
but ran into @objc
when setting my #selector
, then thought about using the block-based version of addObserver(forName:object:queue:using:)
, then passing in a notification handler closure, blah, blah, blah… I decided that my notification handling code would be much more intelligible and educational as an abstract class.
I also realize that Swift has no formal concept of an abstract class, but you probably already know there is a commonly used workaround for creating one. So, again, to simplify my didactic goal of explaining the observer pattern, I made the Observer
class “abstract” by forcing you to override its handleNotification()
method. By doing so, I gave you the opportunity to inject any type of specialized logic you want to have executed whenever your Observer
subclass instance receives a notification.
Shown immediately below is my sample project’s ViewController.swift
file, which shows you how to make use of my core logic in Observable.swift
, which we just discussed and reviewed:
import UIKit // By adopting the ObservedProtocol, this view controller // gains the capability for broadcasting notifications via // NotificationCenter to ANY entities throughout this // WHOLE APP whom are interested in receiving those notifications. // Notice how little this class needs to do to gain // notification capability? class ViewController: UIViewController, ObservedProtocol { @IBOutlet weak var topBox: UIView! @IBOutlet weak var middleBox: UIView! @IBOutlet weak var bottomBox: UIView! // Mock up three entities whom are dependent upon a // critical resource: network connectivity. They need // to observe the status of the critical resource. var networkConnectionHandler0: NetworkConnectionHandler? var networkConnectionHandler1: NetworkConnectionHandler? var networkConnectionHandler2: NetworkConnectionHandler? // ObservedProtocol conformance -- two properties. let statusKey: StatusKey = StatusKey.networkStatusKey let notification: Notification.Name = .networkConnection override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // Here are three entities now listening for notifications // from the enclosing ViewController class. networkConnectionHandler0 = NetworkConnectionHandler(view: topBox) networkConnectionHandler1 = NetworkConnectionHandler(view: middleBox) networkConnectionHandler2 = NetworkConnectionHandler(view: bottomBox) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // Mock up a critical resource whose state can change. // I'm pretending that this ViewController is // responsible for network reachability/connectivity. // When we have a network connection, all interested // listeners are informed. When network access is lost, // all interested listeners are informed. @IBAction func switchChanged(_ sender: Any) { let swtich:UISwitch = sender as! UISwitch if swtich.isOn { notifyObservers(about: NetworkConnectionStatus.connected.rawValue) } else { notifyObservers(about: NetworkConnectionStatus.disconnected.rawValue) } } // end func switchChanged } // end class ViewController
The memento design pattern
Most iOS developers are familiar with the memento pattern. Think of iOS facilities for archives and serialization which allow you to “Convert objects and values to and from property list, JSON, and other flat binary representations.” Think of the iOS state preservation and restoration feature, which remembers and then returns “your app to its previous state after it is terminated by the system.”
The memento design pattern is meant to capture, represent, and store the internal state of an instance at a specific point in time and then allow you to find that instance’s state representation at a later time and restore it. When you restore the state of an instance, it should exactly reflect it’s state at the time of capture. While this may sound obvious, you should ensure that all instance property access levels should be respected during capture and restoration, e.g., public
data should be restored to public
properties and private
data should be restored to private
properties.
To keep things simple, I used iOS’s UserDefaults
as the core of my instance state storage and recovery process.
Use case for memento design pattern app
While I understand that iOS already has facilities for archiving and serialization, I developed sample code that can save and recover an class’s state. My code does a pretty good job of abstracting archiving and de-archiving so that you can store and recover the state of a variety of different instances with varying properties. But my example is not meant for production use. It is a didactic example formulated to explain the memento pattern.
My memento example project, available on GitHub, showcases how a User
class instance’s state, with firstName
, lastName
, and age
properties, can be persisted to UserDefaults
and then later recovered. At first, no User
instance is available to recover, but then I enter one, archive it, and then recover it, as shown here:
Here’s the console output corresponding to the previous video:
Empty entity. lastName: Adams age: 87 firstName: John lastName: Adams age: 87 firstName: John
Sample code for memento design pattern app
My memento code is straightforward. It provides the Memento
protocol and protocol extension for handling and abstracting all the details of gross archiving and de-archiving of the member properties of classes that adopt the Memento
protocol. The extension also allows you to print the entire state of an instance to console at any given point in time. I used a Dictionary<String, String>
to store an adopting class’s property names as keys and property contents as values. I stored values as String
to keep my implementation simple and easily understood, fully well acknowledging that there are many use cases which would require you to archive and de-archive much more complex property types. This is a tutorial about design patterns, not a code base for a production app.
Notice that I added persist()
and recover()
methods to the Memento
protocol, which must be implemented by any class that adopts the Memento
protocol. These methods provide developers with the opportunity to archive and de-archive Memento
protocol-adopting class’s specific properties, by name. In other words, the elements of the state
property of type Dictionary<String, String>
can be matched one-to-one with the Memento
protocol-adopting class’s properties. Each property name corresponds to a dictionary element key and each property value corresponds to the dictionary element’s value which matches said key. Just look at the code and you’ll understand.
Since the persist()
and recover()
methods must be implemented by any class that adopts the Memento
protocol, properties of all access levels, e.g., public
, private
, and fileprivate
are visible to, and accessible by, these methods.
You may wonder why I made the Memento
protocol class-only. I did so because of that horrible Swift compiler message, “Cannot use mutating member on immutable value: ‘self’ is immutable.” Discussing it is way beyond the scope of this tutorial, but if you’d like to torture yourself, read a great description of the issue here.
Here’s my core logic for implementing the memento design pattern, found in file Memento.swift
of my sample app:
import Foundation // I've only limited this protocol to reference types because of the // "Cannot use mutating member on immutable value: ‘self’ is immutable" // conundrum. protocol Memento : class { // Key for accessing the "state" property // from UserDefaults. var stateName: String { get } // Current state of adopting class -- all // property names (keys) and property values. var state: Dictionary{ get set } // Save "state" property with key as specified // in "stateName" into UserDefaults ("generic" save). func save() // Retrieve "state" property using key as specified // in "stateName" from UserDefaults ("generic" restore). func restore() // Customized, "specific" save of "state" dictionary with // keys corresponding to member properties of adopting // class, and save of each property value (class-specific). func persist() // Customized, "specific" retrieval of "state" dictionary using // keys corresponding to member properties of adopting // class, and retrieval of each property value (class-specific). func recover() // Print all adopting class's member properties by // traversing "state" dictionary, so output is of // format: // // Property 1 name (key): property 1 value // Property 2 name (key): property 2 value ... func show() } // end protocol Memento extension Memento { // Save state into dictionary archived on disk. func save() { UserDefaults.standard.set(state, forKey: stateName) } // Read state into dictionary archived on disk. func restore() { if let dictionary = UserDefaults.standard.object(forKey: stateName) as! Dictionary ? { state = dictionary } else { state.removeAll() } } // end func restore() // Storing state in dictionary makes display // of object state so easy and intuitive. func show() { var line = "" if state.count > 0 { for (key, value) in state { line += key + ": " + value + "\n" } print(line) } else { print("Empty entity.\n") } } // end func show() } // end extension Memento // By adopting the Memento protocol, we can, with relative // ease, save the state of an entire class to persistant // storage and then retrieve that state at a later time, i.e., // across different instances of this app running. class User: Memento { // These two properties are required by Memento. let stateName: String var state: Dictionary // These properties are specific to a class that // represents some kind of system user account. var firstName: String var lastName: String var age: String // Initializer for persisting a new user to disk, or for // updating an existing user. The key value used for accessing // persistent storage is property "stateName." init(firstName: String, lastName: String, age: String, stateName: String) { self.firstName = firstName self.lastName = lastName self.age = age self.stateName = stateName self.state = Dictionary () persist() } // end init(firstName... // Initializer for retrieving a user from disk, if one // exists. The key value used for retrieving state from // persistent storage is property "stateName." init(stateName: String) { self.stateName = stateName self.state = Dictionary () self.firstName = "" self.lastName = "" self.age = "" recover() } // end init(stateName... // Save the user's properties to persistent storage. // We intuitively save each property value by making // the keys in the dictionary correspond one-to-one with // this class's property names. func persist() { state["firstName"] = firstName state["lastName"] = lastName state["age"] = age save() // leverage protocol extension } // end func persist() // Read existing user's properties from persistent storage. // After retrieving the "state" dictionary from UserDefaults, // we easily restore each property value because // the keys in the dictionary correspond one-to-one with // this class's property names. func recover() { restore() // leverage protocol extension if state.count > 0 { firstName = state["firstName"]! lastName = state["lastName"]! age = state["age"]! } else { self.firstName = "" self.lastName = "" self.age = "" } } // end func recover } // end class User
Here’s the code for implementing the memento design pattern use case I described earlier (archiving and de-archiving an instance of class User
), as found in my sample app as file ViewController.swift
:
import UIKit class ViewController: UIViewController { @IBOutlet weak var firstNameTextField: UITextField! @IBOutlet weak var lastNameTextField: UITextField! @IBOutlet weak var ageTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // Called when "Save User" button is tapped. Stores // "User" class instance properties to UserDefaults // based on stateName property value of "userKey" (but // use whatever lights your fire). @IBAction func saveUserTapped(_ sender: Any) { if firstNameTextField.text != "" && lastNameTextField.text != "" && ageTextField.text != "" { let user = User(firstName: firstNameTextField.text!, lastName: lastNameTextField.text!, age: ageTextField.text!, stateName: "userKey") user.show() } } // end func saveUserTapped // Called when "Restore User" button is tapped. Retrieves // "User" class instance properties from UserDefaults // based on stateName property value of "userKey," if // a key/value pair with key "userKey" exists. @IBAction func restoreUserTapped(_ sender: Any) { let user = User(stateName: "userKey") firstNameTextField.text = user.firstName lastNameTextField.text = user.lastName ageTextField.text = user.age user.show() } } // end class ViewController
Conclusion
Some critics have claimed that use of design patterns is proof of deficiencies in programming languages and that seeing recurring patterns in code is a bad thing. I disagree. Expecting a language to have a feature for everything is silly and would most likely lead to enormous languages like C++ into becoming even larger and more complex and thus harder to learn, use, and maintain. Recognizing and solving recurring problems is a positive human trait worthy of positive reinforcement. Design patterns are successful examples of learning from history, something humankind has failed to do too many times. Coming up with abstract and standardized solutions to common problems makes those solutions portable and more likely to be distributed.
A combination of a compact language like Swift and an arsenal of best practices, like design patterns, is an ideal and happy medium. Consistent code is generally readable and maintainable code. Remember too that design patterns are ever-evolving as millions of developers are constantly discussing and sharing ideas. By virtue of being linked together over the World Wide Web, this developer discussion has lead to constantly self-regulating collective intelligence.