Swift Tips

Apart from my main weekly article series, my podcast, and my Basics articles — I also love to share brief Swift tips and tricks with the community on Twitter.

This page contains a collection of some of those tips. More tips and tricks will be added very soon, and there are also links to related content on the same topic beneath each tip.

To subscribe to these Swift tips, either follow me on Twitter, or sign up for the monthly Swift by Sundell newsletter.


Refactoring enums which cases contain the same data

Enums with associated values are awesome, but if many cases need to include the same data, it’s perhaps time to refactor that enum into a different solution — for example by wrapping it in a struct that contains the shared data.

Here all of our Warning enum’s cases contains a path, so to reduce duplication and making handling paths easier, we move it to a wrapping struct instead:

// Before

enum Warning {
    case fileUnreadable(path: String)
    case decodingFailed(path: String, DecodingError)
    case unknownError(path: String, Error)
}

// After

struct Warning {
    let path: String
    let reason: Reason
}

extension Warning {
    enum Reason {
        case fileUnreadable
        case decodingFailed(DecodingError)
        case unknownError(Error)
    }
}

Related articles:


Converting between String and Data without optionals

While Swift offers many APIs for converting between Data and String, these two let you do so without any optionals — usually resulting in simpler code:

// String -> Data
let data = Data("Hello, world!".utf8)

// Data -> String
let string = String(decoding: data, as: UTF8.self)

Related content:


Swift packages containing both a command line tool and a library

If we want to distribute a Swift package as both a library that other tools can import, and as a command line tool, all we have to do is to define two separate targets and products.

Here’s an example Package.swift file in which we’re doing exactly that:

// swift-tools-version:5.0

import PackageDescription

let package = Package(
    name: "Plot",
    products: [
        // If we want to distribute a Swift package as both a
        // library and a command line tool, one way to do that
        // is to simply use lower case characters for the CLI.
        .library(name: "Plot", targets: ["Plot"]),
        .executable(name: "plot", targets: ["PlotCLI"])
    ],
    dependencies: [
        .package(url: "https://github.com/johnsundell/files.git", from: "3.1.0"),
        .package(url: "https://github.com/johnsundell/sweep.git", from: "0.2.1"),
        .package(url: "https://github.com/johnsundell/codextended.git", from: "0.2.0")
    ],
    targets: [
        .target(
            name: "Plot",
            dependencies: ["Files", "Sweep", "Codextended"]
        ),
        .target(
            // We can still name our target "CLI", to make it
            // easier to see the difference between our CLI
            // and our library in Xcode.
            name: "PlotCLI",
            dependencies: ["Plot"]
        ),
        .testTarget(
            name: "PlotTests",
            dependencies: ["Plot", "Codextended"]
        )
    ]
)

Related content:

3 different ways of mutating all values in a dictionary

One of my favorite things about programming is that there’s very rarely a “one true way” to do things — we often have multiple potential solutions at our disposal, letting us pick whichever one is the most appropriate for each situation.

Here’s an example of three different ways of mutating all values within a dictionary:

// Making each (key, value) tuple's value mutable

struct Inbox {
    var emails: [Email.ID : Email]

    mutating func markAllAsRead() {
        // When iterating over a collection of tuples (such as
        // keys & values in a dictionary), we can optionally choose
        // to make a member mutable by prefixing it with 'var'.
        for (id, var email) in emails {
            email.wasRead = true
            emails[id] = email
        }
    }
}

// Using mapValues (added in Swift 4)

struct Inbox {
    var emails: [Email.ID : Email]

    mutating func markAllAsRead() {
        // Another way to achieve the same thing as above would
        // be to use 'mapValues' instead, which lets us mutate
        // each value within a dictionary.
        emails = emails.mapValues { email in
            var email = email
            email.wasRead = true
            return email
        }
    }
}

// Iterating over all of the dictionary's keys

struct Inbox {
    var emails: [Email.ID : Email]

    mutating func markAllAsRead() {
        // And yet another option would be to instead iterate
        // over all of the dictionary's keys and perform an
        // inline mutation.
        for id in emails.keys {
            emails[id]?.wasRead = true
        }
    }
}

Related articles:


Swift’s composition operator

Swift’s & operator is really powerful. Not only does it let us compose multiple protocols, we can also use it to combine concrete types and protocols as well:

protocol Reloadable {
    func reload()
}

// Using the '&' operator, we can not only compose multiple
// protocols — we can also combine a concrete type with a
// protocol to form a brand new type.
typealias ReloadableViewController = UIViewController & Reloadable

// Our above type alias acts like a proper type in most cases,
// we can even inherit directly from it!
class ProfileViewController: ReloadableViewController {
    private lazy var tableView = UITableView()

    func reload() {
        tableView.reloadData()
    }
}

Related articles:


Generic algorithms

A big benefit of Swift’s protocol-oriented design is that it makes it much easier to implement generic algorithms that can be used in many different contexts. For example, here we’re not only extending Array - we’re extending all RandomAccessCollection types:

extension RandomAccessCollection {
    func element(at index: Index) -> Element? {
        guard indices.contains(index) else {
            return nil
        }

        return self[index]
    }
}

By implementing extensions on standard library protocols instead of concrete types, our added functionality can be used in so many more contexts. For example, the above extension can now be used not only on Array, but also on Data, many kinds of ranges, IndexPath, KeyValuePairs, and many more.

Related articles:

Protocols “inheriting” from classes

New in Swift 5 is that protocols can now ”inherit” from classes — requiring all types conforming to that protocol to also inherit from the specified class:

// Here the Component protocol "inherits" from UIView, essentially
// requiring all types conforming to the protocol to also inherit
// from the same class.
protocol Component: UIView {
    associatedtype Model

    func activate(with model: Model)
    func deactivate()
}

// This is especially useful when one of our protocols really
// only makes sense within a certain context — such as UIViews
// in this case.
class ImageComponent: UIImageView, Component {
    func activate(with model: UIImage) {
        image = model
    }

    func deactivate() {
        image = nil
    }
}

This is essentially new syntactic sugar around protocol constraints/specialization, which you can read more about in "Specializing protocols in Swift”, linked below.

Related articles:


The ‘final’ keyword

Swift's final keyword is not only useful in order to prevent a class from being subclassed entirely, it can also let us mark individual methods as non-overridable — which is useful when relying on subclassing, and we still want to protect certain methods from being modified:

// A class that isn't 'final' can be subclassed within its own
// module (outside of the module, only if it's marked as 'open').
class Actor {
    func willMove(to scene: Scene) {
        ...
    }

    func didMove(to scene: Scene) {
        ...
    }

    // However, individual functions can still be marked as
    // 'final' to prevent overrides.
    final func remove() {
        ...
    }
}

extension Actor {
    // Methods defined in extensions can't be overriden either,
    // at least not yet.
    func add(to scene: Scene) {
        ...
    }
}

class Player: Actor {
    // This is especially useful when setting up a hierarchy
    // of classes that derive from a parent class, and we only
    // want to enable specific overrides.
    override func willMove(to scene: Scene) {
        super.willMove(to: scene)
        ...
    }
}

The power of key paths

Swift’s key paths can let us build APIs that are both really powerful, but also look really nice and clean at the call site. Here we have an extension that lets us easily group any Sequence based on an Element key path:

// This extension will let us group any Sequence (such as an
// Array or a Set), based on a given key path.
extension Sequence {
    func grouped<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [T: [Element]] {
        // Using key path subscripting syntax, we can dynamically
        // access a member of a type based on a key path.
        return .init(grouping: self, by: { $0[keyPath: keyPath] })
    }
}

func scan(_ string: String, using matchers: [Matcher]) {
    // The result is that our call sites become really clean, since
    // we can simply use a key path literal to group any sequence.
    let matchersByPattern = (
        start: matchers.grouped(by: \.pattern.start),
        end: matchers.grouped(by: \.pattern.end)
    )

    ...
}

Related articles:


Recursive enums with “fake” cases

Indirect enums can provide a great way to set up recursive data structures with a finite number of cases, and static functions can let us define “fake” cases that look like normal cases, but don’t require additional handling in switches:

// Indirect enums are able to reference themselves, enabling
// us to set up recursive data structures.
indirect enum Content {
    case values(Values)
    case script(Script)
    case collection([Content])
}

extension Content {
    // Using static functions, we can create "fake" enum cases,
    // that can act as convenience APIs — without having to add
    // additional case handling code in our switch statements.
    static func text(_ text: String) -> Content {
        return .values(["content": text])
    }
}