Articles, podcasts and news about Swift development, by John Sundell.

5 small but significant improvements in Swift 5.1

Published on 22 Sep 2019
Discover page available: The Standard Library

Swift 5.1 has now been officially released, and despite being a minor release, it contains a substantial number of changes and improvements — ranging from fundamental new features, like module stability (which enables SDK vendors to ship pre-compiled Swift frameworks), to all of the new syntax features that power SwiftUI, and beyond.

Besides its headlining new features, Swift 5.1 also contains a number of smaller — but still highly significant — new capabilities and improvements. It’s the sort of changes that at first may seem really minor, or even unnecessary, but can turn out to have a quite major impact on how we write and structure our Swift code. This week, let’s take a look at five of those features, and what kind of situations they could be useful in.

Memberwise initializers with default values

One of the many things that make structs so appealing in Swift is their auto-generated “memberwise” initializers — which enable us to initialize any struct (that doesn’t contain private stored properties) simply by passing values corresponding to each of its properties, like this:

struct Message {
    var subject: String
    var body: String
}

let message = Message(subject: "Hello", body: "From Swift")

These synthesized initializers have been significantly improved in Swift 5.1, since they now take default property values into account, and automatically translate those values into default initializer arguments.

Let’s say that we wanted to expand the above Message struct with support for attachments, but that we’d like the default value to be an empty array — and at the same time, we’d also like to enable a Message to be initialized without having to specify a body up-front, so we’ll give that property a default value as well:

struct Message {
    var subject: String
    var body = ""
    var attachments: [Attachment] = []
}

In Swift 5.0 and earlier, we’d still have to pass initializer arguments for all of the above properties anyway, regardless of whether they have a default value. However, in Swift 5.1, that’s no longer the case — meaning that we can now initialize a Message by only passing a subject, like this:

var message = Message(subject: "Hello, world!")

That’s really cool, and it makes using structs even more convenient than before. But perhaps even cooler is that, just like when using standard default arguments, we can still override any default property value by passing an argument for it — which gives us a ton of flexibility:

var message = Message(
    subject: "Hello, world!",
    body: "Swift 5.1 is such a great update!"
)

However, while memberwise initializers are incredibly useful within an app or module, they’re still not exposed as part of a module’s public API — meaning that if we’re building some form of library or framework, we still have to define our public-facing initializers manually (for now).

Using Self to refer to enclosing types

Swift’s Self keyword (or type, really) has previously enabled us to dynamically refer to a type in contexts where the actual concrete type isn’t known — for example by referring to a protocol’s implementing type within a protocol extension:

extension Numeric {
    func incremented(by value: Self = 1) -> Self {
        return self + value
    }
}

While that’s still possible, the scope of Self has now been extended to also include concrete types — like enums, structs and classes — enabling us to use Self as a sort of alias referring to a method or property’s enclosing type, like this:

extension TextTransform {
    static var capitalize: Self {
        return TextTransform { $0.capitalized }
    }

    static var removeLetters: Self {
        return TextTransform { $0.filter { !$0.isLetter } }
    }
}

The fact that we can now use Self above, rather than the full TextTransform type name, is of course purely syntactic sugar — but it can help make our code a bit more compact, especially when dealing with long type names. We can even use Self inline within a method or property as well, further making the above code even more compact:

extension TextTransform {
    static var capitalize: Self {
        return Self { $0.capitalized }
    }

    static var removeLetters: Self {
        return Self { $0.filter { !$0.isLetter } }
    }
}

Besides referring to an enclosing type itself, we can now also use Self to access static members within an instance method or property — which is quite useful in situations when we want to reuse the same value across all instances of a type, such as the cellReuseIdentifier in this example:

class ListViewController: UITableViewController {
    static let cellReuseIdentifier = "list-cell"

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(
            ListTableViewCell.self,
            forCellReuseIdentifier: Self.cellReuseIdentifier
        )
    }
}

Again, we could’ve simply typed out ListViewController above when accessing our static property, but using Self does arguably improve the readability of our code — and will also enable us to rename our view controller without having to update the way we access its static members.

Switching on optionals

Next, let’s take a look at how Swift 5.1 makes it easier to perform pattern matching on optionals, which really comes in handy when switching on an optional value. As an example, let’s say that we’re working on a music app that contains a Song model — which has a downloadState property that lets us keep track of whether a song has been downloaded, if it’s currently being downloaded, and so on:

struct Song {
    ...
    var downloadState: DownloadState?
}

The reason the above property is an optional is that we want nil to represent the lack of a download state, that is, if a song hasn’t been downloaded at all.

Like we took a look at in “Pattern matching in Swift”, Swift’s advanced pattern matching capabilities enable us to directly switch on an optional value — without having to unwrap it first — however, before Swift 5.1, doing so required us to append a question mark to each matching case, like this:

func songDownloadStateDidChange(_ song: Song) {
    switch song.downloadState {
    case .downloadInProgress?:
        showProgressIndiator(for: song)
    case .downloadFailed(let error)?:
        showDownloadError(error, for: song)
    case .downloaded?:
        downloadDidFinish(for: song)
    case nil:
        break
    }
}

In Swift 5.1, those trailing question marks are no longer needed, and we can now simply refer to each case directly — just like when switching on a non-optional value:

func songDownloadStateDidChange(_ song: Song) {
    switch song.downloadState {
    case .downloadInProgress:
        showProgressIndiator(for: song)
    case .downloadFailed(let error):
        showDownloadError(error, for: song)
    case .downloaded:
        downloadDidFinish(for: song)
    case nil:
        break
    }
}

While the above is a really welcome change in terms of further reducing the syntax required to implement common patterns, it does come with a slight side-effect, that could potentially be source-breaking for certain enums and switch statements.

Since Swift optionals are implemented using the Optional enum under the hood, we’re no longer able to perform the above kind of optional pattern matching on any enum that contains either a some or none case — since those will now conflict with the cases that Optional contains.

However, it can be argued that any enum that contains such cases (especially none), should instead be implemented using an optional — since representing potentially missing values is essentially what optionals do.

The Identifiable protocol

Originally introduced as part of the initial release of SwiftUI, the new Identifiable protocol has now made its way into the Swift standard library — and provides a simple and unified way to mark any type as having a stable, unique identifier.

To conform to this new protocol, we simply have to declare an id property, which can contain any Hashable type — for example String:

struct User: Identifiable {
    typealias ID = String

    var id: ID
    var name: String
}

Similar to when Result was added to the standard library as part of Swift 5.0, a major benefit of now having Identifable accessible to any Swift module is that it can be used to share requirements across different code bases.

For example, using a constrained protocol extension, we could add a convenience API for transforming any Sequence that contains identifiable elements into a dictionary — and then vend that extension as part of a library, without requiring us to define any protocol of our own:

public extension Sequence where Element: Identifiable {
    func keyedByID() -> [Element.ID : Element] {
        var dictionary = [Element.ID : Element]()
        forEach { dictionary[$0.id] = $0 }
        return dictionary
    }
}

The above API is implemented as a method, rather than as a computed property, since its time complexity is O(n). For more on picking between a method and a computed property, see this article.

However, while the standard library’s new Identifiable protocol is really useful when dealing with collections of values that each have a stable identifier, it doesn’t do much to improve the actual type safety of our code.

Since all that Identifiable does is requiring us to define any hashable id property, it won’t protect us from accidentally mixing up identifiers — such as in this situation, when we’re mistakenly passing a User ID to a function that accepts a Video ID:

postComment(comment, onVideoWithID: user.id)

So there’s still quite a lot of strong use cases for a proper Identifier type and a more robust Identifiable protocol — such as the ones we took a look at in “Type-safe identifiers in Swift”, which prevents the above kind of mistakes from happening. However, it’s still really nice to now have some version of an Identifiable protocol in the standard library, even if it is a bit more limited.

Ordered collection diffing

Finally, let’s take a look at a brand new standard library API that’s being introduced as part of Swift 5.1 — ordered collection diffing. As we, as a community, move closer and closer to the world of declarative programming with tools like Combine and SwiftUI — being able to calculate the difference between two states is something that becomes increasingly important.

After all, declarative UI development is all about continuously rendering new snapshots of state — and while SwiftUI and the new diffable data sources will probably do most of the heavy lifting to make that happen — being able to calculate a diff between two states ourselves could be incredibly useful.

For example, let’s say that we’re building a DatabaseController that’ll let us easily update our on-disk database with an array of in-memory models. To be able to figure out whether a model should be inserted or deleted, we can now simply call the new difference API to calculate the diff between our old array and the new one — and then iterate through the changes within that diff in order to perform our database operations:

class DatabaseController<Model: Hashable & Identifiable> {
    private let database: Database
    private(set) var models: [Model] = []
    
    ...

    func update(with newModels: [Model]) {
        let diff = newModels.difference(from: models)

        for change in diff {
            switch change {
            case .insert(_, let model, _):
                database.insert(model)
            case .remove(_, let model, _):
                database.delete(model)
            }
        }

        models = newModels
    }
}

However, the above implementation does not account for moved models — since moves will, by default, be treated as separate insertions and removals. To fix that, let’s also call the inferringMoves method when computing our diff — and then look at whether each insert was associated with a removal, and if so treat it as a move instead, like this:

func update(with newModels: [Model]) {
    let diff = newModels.difference(from: models).inferringMoves()
    
    for change in diff {
        switch change {
        case .insert(let index, let model, let association):
            // If the associated index isn't nil, that means
            // that the insert is associated with a removal,
            // and we can treat it as a move.
            if association != nil {
                database.move(model, toIndex: index)
            } else {
                database.insert(model)
            }
        case .remove(_, let model, let association):
            // We'll only process removals if the associated
            // index is nil, since otherwise we will already
            // have handled that operation as a move above.
            if association == nil {
                database.delete(model)
            }
        }
    }
    
    models = newModels
}

The fact that diffing is now built into the standard library (and also into UIKit and AppKit) is fantastic news — as writing an efficient, flexible, and robust diffing algorithm can be incredibly difficult.

Conclusion

Not only is Swift 5.1 a key enabler for SwiftUI and Combine, it’s also big news for any team that vends pre-compiled frameworks, as Swift is now not only ABI stable, but also module stable. On top of that, Swift 5.1 also includes many small but welcome changes and tweaks that should be applicable to almost any code base — and while we’ve taken a look at five of those changes in this article, we’ll keep diving deeper into more aspects of Swift 5.1 within the coming weeks and months.

The reason no SwiftUI-related features were included in this article is that those were covered in “The Swift 5.1 features that power SwiftUI’s API”. The same is also true for static subscripts, which were covered in “The power of subscripts in Swift”.

What do you think? Have you already migrated your projects to Swift 5.1, and if so, what’s your favorite new feature? Let me know — along with any questions, comments or feedback that you might have — either via Twitter or email.

Thanks for reading! 🚀