Blog

Latest Post

Swift Tip: Protocols vs. Values

Last week, we compared enums and protocols in terms of extensibility. This week we’ll explore how regular values can be used instead of a protocol, and how the two approaches differ in terms of extensibility. We learn that a type can conform to a protocol at most once, whereas you can have many “instances” of regular values.

In the tiny networking library we built for Swift Talk, we use a simple struct to describe a resource that can be loaded from the network:

import Foundation

struct Resource<A> {
    var request: URLRequest
    var parse: (Data) throws -> A
}

This struct contains all the information needed to make the request (in the URLRequest value), as well as a function that knows how to turn the data from the network into an A, whatever that type might be.

An alternative approach would be to define a protocol to which types loadable from the network can conform:

protocol Loadable {
    static var request: URLRequest { get }
    init(parsing data: Data) throws
}

This protocol defines the same requirements as the Resource struct above, just in a slightly different form: a static property for the URLRequest, and a throwing initializer to turn data into an instance of conforming types.

Let’s say we want to load a list of countries from a webserver with either one of these approaches. First, we need a Country type:

struct Country: Codable {
    var alpha2Code: String
    var name: String
    var population: Int
}

Since Country conforms to Codable, we can use the JSONDecoder to turn the incoming data into Country values. To avoid writing this boilerplate code for each JSON endpoint, we can define an initializer on the Resource struct for types that are Decodable:

extension Resource where A: Decodable {
    init(get url: URL) {
        self.init(request: URLRequest(url: url)) { data in
            try JSONDecoder().decode(A.self, from: data)
        }
    }
}

For the protocol approach, we can add the same convenience with a default implementation of the init(parsing:) initializer in case the type conforms to Decodable:

extension Loadable where Self: Decodable {
    init(parsing data: Data) throws {
        self = try JSONDecoder().decode(Self.self, from: data)
    }
}

To load the countries using the Resource approach, we first have to create a resource describing the endpoint. Then we use a custom load method on URLSession to load the data (for the full code see this gist).

let countries = Resource<[Country]>(get: URL(string: "https://restcountries.eu/rest/v2/all")!)
URLSession.shared.load(countries) { print($0) }

To do the same with the protocol based approach, we have to conform Array to Loadable if its elements are of type Country:

extension Array: Loadable where Element == Country {
    static let request = URLRequest(url: URL(string: "https://restcountries.eu/rest/v2/all")!)
}

URLSession.shared.load([Country].self) { print($0) }

Unfortunately, here we run into a limitation of protocols: each type can conform to a protocol at most once. For example, we can’t conform Array again for a different type of element. We might try to conform Array when its elements are Loadable:

extension Array: Loadable where Element: Loadable {
    static let request = ...?
}

However, since Loadable types need to specify the URL they can be loaded from, this approach doesn’t work either. We can’t specify a URL for a generic array of Loadable elements.

Being able to conform to protocols only once is also a problem for protocols such as Codable. For example, if Apple ever provides conformance for CLLocationCoordinate2D, it has to pick a single representation. In the API we’ve used above, a location coordinate is represented as an array of numbers, but we have also used APIs where it’s represented as {lat: 39, lon: 22} or {latitude: 39, longitude: 22}. JSONDecoder solves this problem by providing options for common variations, like date formats. However, if the decoder doesn’t have support for a format you need, we have to resort to using a wrapper type, as we’ve discussed in a previous post.

When designing your own APIs, think twice about whether it would make sense to conform a type multiple times. If yes, try using values like the Resource struct rather than protocols.

You can read much more about protocols in our book, Advanced Swift. A newly expanded edition is almost ready for release, and the update will be free for everyone who already owns the Ebook. πŸ‘

Previous Posts

Swift Tip: Enums vs. Protocols

This week, we’ll look at how protocols and enums are extensible in different ways. In a previous post, we discussed the expression problem as it relates to classes and structs, and there are several similarities.

In many cases, we can model data as either an enum or a protocol.

Consider the following:

enum Shape {
    case circle(boundingBox: CGRect)
    case rectangle(rect: CGRect)
    case combined(top: Shape, bottom: Shape)
}

extension Shape {
   func draw(in context: CGContext) {
      // ...
   }
}

We can extend the enum in two orthogonal directions: we can add new methods (or computed properties), or we can add new cases. Adding new methods won’t break existing code. Adding a new case, however, will break any switch statement that doesn’t have a default case.

To model this using protocols, we would create a struct for every case, then put the draw method in the protocol:

protocol Drawable {
    func draw(in context: CGContext)
}

struct Rectangle: Drawable {
    let rect: CGRect
    func draw(in context: CGContext) {
        // ...
    }
}

struct Ellipse: Drawable {
    let boundingBox: CGRect
    func draw(in context: CGContext) {
        // ...
    }
}

struct Combined<A, B> {
    let top: A
    let bottom: B
}

extension Combined: Drawable where A: Drawable, B: Drawable {
    func draw(in context: CGContext) {
        bottom.draw(in: context)
        top.draw(in: context)
    }
}

When we consider extensibility, the protocol approach allows us to add new conformances without breaking existing code. However, when we add a new requirement to the protocol, this does break existing code (except in those cases where we can provide a default implementation).

Looking at Swift’s standard library, we can see these extensibility tradeoffs manifested in the types. Result and Optional are defined as enums; both the standard library and our own code can add extensions without having to worry about breaking things. However, we can’t add new cases to the enum. If the standard library implementers were to do so, everyone’s code would break.

Likewise, Collection and Sequence are defined as protocols. Both the standard library and our own code can easily add new “cases” (that is, conform our own types) without breaking existing code. However, we can’t change the protocol ourselves. The standard library implementers have to be very careful about changes. If they were to add another requirement to Collection, for example, that could break every custom collection conformance outside of the standard library.

In most cases, the decision to use an enum or a protocol should be pretty clear. However, when it’s unclear, and especially when you’re writing a framework, consider how you want the type to be extended. Needless to say, there are often other options too!

To learn more about building extensible libraries, watch our public Swift Talk episode, Enums vs. Classes, recorded with Brandon Kase.

The new edition of our book, Advanced Swift, is almost ready for release. It includes a new chapter on enums, and a revised chapter on protocols, all updated for Swift 5 β€”Β you can read an excerpt here.

Swift Tip: Reading from Standard Input/Output

The open-source Markdown Playgrounds app we’ve been documenting on Swift Talk uses the Swift REPL to execute Swift code blocks. This means that we’re reading the standard output of the REPL process to show the results:

stdOut = Pipe()
process = Process()
process.launchPath = "/usr/bin/swift"
process.standardOutput = stdOut
process.launch()

token = NotificationCenter.default.addObserver(forName: .NSFileHandleDataAvailable, object: stdOut.fileHandleForReading, queue: nil) { _ in
    let handle = note.object as! FileHandle
    // Read the available data ...
    handle.waitForDataInBackgroundAndNotify()
}

stdOut.fileHandleForReading.waitForDataInBackgroundAndNotify()

We reach the important part of this process when we start reading data from the file handle. The REPL output is UTF-8 encoded, so the data we’re reading consists of UTF-8 code units. Since we’re receiving the data in chunks, we’re unable to assume that the chunks end on valid character boundaries.

For example, if we naively turn the available data into a string, we might run into issues like this:

148 1@2x

To avoid such issues, we have to buffer the incoming data and only transform it into a string when we’re sure that the data doesn’t end on an incomplete character. For our use case, we can determine this by waiting for newline characters.

We can abstract this logic into a simple struct:

struct REPLBuffer {
    private var buffer = Data()

    mutating func append(_ data: Data) -> String? {
        buffer.append(data)
        if let string = String(data: buffer, encoding: .utf8), string.last?.isNewline == true {
            buffer.removeAll()
            return string
        }
        return nil
    }
}

We can append to this buffer each time the file handle has data available. If the data ends on a newline, it returns the string β€” otherwise it just returns nil:

var buffer = REPLBuffer()
token = NotificationCenter.default.addObserver(forName: .NSFileHandleDataAvailable, object: stdOut.fileHandleForReading, queue: nil, using: { _ in
    let handle = note.object as! FileHandle
    if let string = buffer.append(handle.availableData) {
        print(string)
    }
    // ...
})

This pattern isn’t just useful for reading the standard output of a REPL process, you can use it whenever you’re reading from a file handle where the data might be incomplete.

The Markdown Playgrounds macOS app is open-source, you can check out the code or try it for yourself. We give a five minute overview of the app at the beginning of Swift Talk 145 (a public episode).

To support our work, subscribe to Swift Talk, or give a subscription as a gift.

Swift Tip: Unicode Scalar Properties

As part of the ongoing rewrite of Advanced Swift, this week we turn our attention to the chapter on Strings.

One of our favorite new additions to Swift’s Strings API is the Unicode.Scalar.Properties type.

Before Swift 5, we had to use Foundation’s CharacterSet to check certain Unicode properties. For example, to know whether something is a newline or a space, you could do CharacterSet.whitespacesAndNewlines.contains(c). While the name is CharacterSet, it really should have been called UnicodeScalarSet in Swift, because it only works on unicode scalars.

In Swift 5, we now have a built-in way to check Unicode properties. For instance, we can check if a name starts with an uppercase letter:

for name in ["Alice", "bob", "ĎÑbel"] {
    print(name.unicodeScalars.first!.properties.isUppercase)
}
// Prints true, false, true

We can also query other kinds of properties:

print(String("1+∫".unicodeScalars.filter { $0.properties.isMath }))
// Prints +∫

It’s also possible to get a scalar’s official Unicode name:

print("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§\n⌘".unicodeScalars.map { $0.properties.name })

This prints the following (note that the πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ emoji consist of 7 scalars):

[Optional("MAN"), Optional("ZERO WIDTH JOINER"),
Optional("WOMAN"), Optional("ZERO WIDTH JOINER"),
Optional("GIRL"), Optional("ZERO WIDTH JOINER"), Optional("GIRL"),
nil,  // The newline
Optional("PLACE OF INTEREST SIGN")]

This new API feels much more native to Swift (previously, you would achieve the same using Foundation’s StringTransform), which is really nice to see.

The new edition of Advanced Swift should be released at the beginning of May. The update will be free for all existing Ebook readers. πŸ‘

Swift Tip: Exhaustive Switching with Enums

As you may already know, we’re currently updating our book, Advanced Swift, for Swift 5. As part of the rewrite, we’re adding an entirely new chapter on enums, a work-in-progress by our co-author Ole Begemann.

Below is an excerpt from this new chapter, taken from the section “Designing with Enums”. It details the benefits of exhaustive switching.


For the most part, switch is just a more convenient syntax for an if case statement with multiple else if case clauses. Syntax differences aside, there’s one important distinction: a switch statement must be exhaustive, i.e. its cases must cover all possible input values. The compiler enforces this.

Exhaustiveness checking is an important tool for writing safe code, and particularly for keeping code correct as programs change. Every time you add a case to an existing enum, the compiler can alert you to all places where you switch over this enum and need to handle the new case. Exhaustiveness checking isn’t performed for if statements, nor does it work in switch statements that include a default clause β€” such a switch can never not be exhaustive since default matches any value.

For this reason, we recommend you avoid default clauses in switch statements if at all possible. You can’t avoid them completely because the compiler isn’t always smart enough to determine if a set of cases is in fact exhaustive (the compiler only ever errs on the side of safety, i.e. it will never report a non-exhaustive set of patterns as exhaustive). We saw an example of this above when we switched over an Int8 and our patterns covered all possible values.

False negatives aren’t a problem when switching over enums though. Exhaustiveness checks are one hundred percent reliable for the following types:

  • Bool
  • Enums, as long as any associated values can be checked exhaustively or you match them with patterns that match any value (wildcard or value-binding pattern)
  • Tuples, as long as their member types can be checked exhaustively

Let’s look at an example. Here we switch over the Shape enum we defined above:

enum Shape {
    case line(from: Point, to: Point)
    case rectangle(origin: Point, width: Double, height: Double)
    case circle(center: Point, radius: Double)
}

let shape = Shape.line(from: Point(x: 1, y: 1), to: Point(x: 3, y: 3))

switch shape {
case let .line(from, to) where from.y == to.y:
    print("Horizontal line")
case let .line(from, to) where from.x == to.x:
    print("Vertical line")
case .line(_, _):
    print("Oblique line")
case .rectangle, .circle:
    print("Rectangle or circle")
}

We include two where clauses to special-case horizontal (equal y coordinates) and vertical (equal x coordinates) lines. These two cases aren’t enough to cover the .line case exhaustively, so we need another clause that catches the remaining lines. Although we’re not interested in distinguishing between .rectangle and .circle here, we prefer listing the remaining cases explicitly over using a default clause.

By the way, the compiler also verifies that every pattern in a switch carries its weight. If the compiler can prove that a pattern will never match because it’s already covered in full by one or more preceding patterns, it will emit a warning.

In this discussion about the benefits of exhaustiveness checking, we have assumed that enums and the code that works with them evolve in sync, i.e. every time a case is added to an enum, the code that switches over that enum can be updated at once. This is usually true if you have access to the source code of your program’s dependencies and the program and its dependencies are compiled together. Things become more complicated when libraries are distributed as binaries and the programs using them must be prepared to work with a newer version of the library than was known when the program was compiled. In this situation, it can be necessary to always include a default clause even in otherwise exhaustive switches. We’ll come back to this in the section “Frozen and Non-Frozen Enums” later in this chapter.


We’re aiming to release the new edition of Advanced Swift at the beginning of May. The update will be free for all existing Ebook readers!