If you're watching this, then I assume you're familiar with Swift extensions. A Swift extension allows you to add functionality to a type, a class, a struct, an enum, or a protocol. But extensions are more powerful than that. In this episode, I'd like to show you four clever uses of Swift extensions.
Protocol Conformance
The Swift Programming Language mentions that extensions can be used to conform an existing type to a protocol. While this isn't new or revolutionary, it can also help you keep your code organized.
Take the UITableViewDataSource
and UITableViewDelegate
protocols as an example. This example may look familiar. This is fine, but it results in a lengthy class implementation that can become difficult to navigate over time.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
...
}
You can keep your code organized by creating an extension for each protocol the type conforms to.
import UIKit
class ViewController: UIViewController {
...
}
extension ViewController: UITableViewDataSource {
...
}
extension ViewController: UITableViewDelegate {
...
}
Navigating source files also becomes easier if you make it a habit to use the jump bar at the top of Xcode's source editor.
Preserving Initializers
I learned the next trick from Chris Eidhof. For this example, we first need to define a structure, Person
. The structure defines two constant properties of type String
, first
and last
.
struct Person {
// MARK: - Properties
let first: String
let last: String
}
Swift generously creates an initializer for us, init(first:last:)
, which we can use to instantiate an instance of the Person
structure. This isn't new.
let john = Person(first: "John", last: "Doe")
Unfortunately, the initializer is no longer available if we define a custom initializer in the struct's definition.
struct Person {
// MARK: - Properties
let first: String
let last: String
// MARK: - Initialization
init(dictionary: [String: String]) {
self.first = dictionary["first"] ?? "John"
self.last = dictionary["last"] ?? "Doe"
}
}
Fortunately, we have an easy workaround to resolve this issue. We create an extension for the Person
struct in which we define the custom initializer.
struct Person {
// MARK: - Properties
let first: String
let last: String
}
extension Person {
// MARK: - Initialization
init(dictionary: [String: String]) {
self.first = dictionary["first"] ?? "John"
self.last = dictionary["last"] ?? "Doe"
}
}
Code Separation
We can take the previous example one step further. A few years ago, Natasha Murashev outlined a technique that uses extensions to separate state from behavior. If we apply this technique to the previous example, we end up with something like this.
struct Person {
// MARK: - Properties
let first: String
let last: String
}
extension Person {
// MARK: - Initialization
init(dictionary: [String: String]) {
self.first = dictionary["first"] ?? "John"
self.last = dictionary["last"] ?? "Doe"
}
// MARK: - Public API
var asDictionary: [String: String] {
return [ "first": first,
"last": last ]
}
}
The type definition only defines the stored properties. An extension is created for the behavior of the type, that is, methods and computed properties. The result is a clear separation of state (stored properties) and behavior (methods and computed properties).
We can take this one step further by creating a second private extension for private behavior.
struct Person {
// MARK: - Properties
let first: String
let last: String
}
extension Person {
...
}
private extension Person {
...
}
Code separation and organization is very easy to do using extensions. I use it all the time. If you miss Objective-C's header files, then this is a nice alternative.
Nested Types
The Swift Programming Language mentions that extensions also allow you to define and use nested types. But I feel this feature is undervalued. I use it in every Swift project, for example, to define constants.
A few months ago, I published a tutorial about building a custom control using a bitmask. In that tutorial, we store the raw value of the bitmask in the user defaults database.
// MARK: - Actions
@IBAction func scheduleDidChange(_ sender: SchedulePicker) {
// Helpers
let userDefaults = UserDefaults.standard
// Store Value
let scheduleRawValue = sender.schedule.rawValue
userDefaults.set(scheduleRawValue, forKey: UserDefaults.Keys.schedule)
}
Instead of using a string literal, we use a constant. We create an extension for the UserDefaults
class in which we define an enum without cases, Keys
. The enum defines one static constant property of type String
, schedule
.
extension UserDefaults {
enum Keys {
static let schedule = "schedule"
}
}
The result is quite nice if you ask me. Not only can we group constants, avoiding literals that are scattered throughout the codebase, we also namespace the constants. In other words, the constants are easy to remember and make sense.
UserDefaults.Keys.schedule
With the release of Swift 3, Apple adopted a similar technique in some of its frameworks.
Notification.Name.UIApplicationWillTerminate
What's Next
Extensions are quite powerful in Swift and the techniques I showed in this tutorial are only a few examples of what's possible.