NEW: Get your ticket for Hacking with Swift Live 2019! >>

What’s new in Swift 5.1

Paul Hudson       @twostraws

Swift 5.0 arrived in Xcode 10.2, but work has been underway on its successor for months and there’s already lots to look forward to.

As with Swift 5.0, 5.1 has a keystone feature in the form of module stability, which allows us to use third-party libraries without worrying which version of the Swift compiler they were built with. That sounds similar to the ABI stability we got in Swift 5.0, but there’s a subtle difference: ABI stability resolves Swift differences at runtime, but module stability resolves differences at compile time.

Alongside that major milestone we’re also getting a number of important language improvements, and in this article I’ll walk through them and provide code examples so you can see them in action.

Universal Self

SE-0068 expands Swift’s use of Self so that it refers to the containing type when used inside classes, structs, and enums. This is particularly useful for dynamic types, where the exact type of something needs to be determined at runtime.

As an example, consider this code:

class NetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }

    func printDebugData() {
        print("Maximum network requests: \(NetworkManager.maximumActiveRequests).")
    }
}

That declares a static maximumActiveRequests property for a network manager, and adds a printDebugData() method to print the static property. That works fine right now, but when NetworkManager is subclassed things become more complicated:

class ThrottledNetworkManager: NetworkManager {
    override class var maximumActiveRequests: Int {
        return 1
    }
}

That subclass changes maximumActiveRequests so that it allows only one request at a time, but if we call printDebugData() it will print out the value from its parent class:

let manager = ThrottledNetworkManager()
manager.printDebugData()

That should print out 1 rather than 4, and that’s where SE-0068 comes in: we can now write Self (with a capital S) to refer to the current type. So, we can rewrite printDebugData() to this:

class ImprovedNetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }

    func printDebugData() {
        print("Maximum network requests: \(Self.maximumActiveRequests).")
    }
}

This means Self works the same way as it did for protocols in earlier Swift versions.

Warnings for ambiguous none cases

Swift’s optionals are implemented as an enum of two cases: some and none. This gave rise to the possibility of confusion if we created our own enums that had a none case, then wrapped that inside an optional.

For example:

enum BorderStyle {
    case none
    case solid(thickness: Int)
}

Used as a non-optional this was always clear:

let border1: BorderStyle = .none
print(border1)

That will print “none”. But if we used an optional for that enum – if we didn’t know what border style to use – then we’d hit problems:

let border2: BorderStyle? = .none
print(border2)

That prints “nil”, because Swift assumes .none means the optional is empty, rather than an optional with the value BorderStyle.none.

In Swift 5.1 this confusion now prints a warning: “Assuming you mean 'Optional.none'; did you mean 'BorderStyle.none' instead?” This avoids the source compatibility breakage of an error, but at least informs developers that their code might not quite mean what they thought.

Matching optional enums against non-optionals

Swift has always been smart enough to handle switch/case pattern matching between optionals and non-optionals for strings and integers, but before Swift 5.1 that wasn’t extended to enums.

Well, in Swift 5.1 we can now use switch/case pattern matching to match optional enums with non-optionals, like this:

enum BuildStatus {
    case starting
    case inProgress
    case complete
}

let status: BuildStatus? = .inProgress

switch status {
case .inProgress:
    print("Build is starting…")
case .complete:
    print("Build is complete!")
default:
    print("Some other build status")
}

Swift is able to compare the optional enum directly with the non-optional cases, so that code will print “Build is starting…”

Ordered collection diffing

SE-0240 introduces the ability to calculate and apply the differences between ordered collections. This could prove particularly interesting for developers who have complex collections in table views, where they want to add and remove lots of items smoothly using animations.

The basic principle is straightforward: Swift 5.1 gives us a new difference(from:) method that calculates the differences between two ordered collections – what items to remove and what items to insert. This can be used with any ordered collection that contains Equatable elements.

To demonstrate this, we can create an array of scores, calculate the difference from one to the other, then loop over those differences and apply each one to make our two collections the same.

Note: Because Swift now ships inside Apple’s operating systems, new features like this one must be used with an #available check to make sure the code is being run on an OS that includes the new functionality. For features that will land in an unknown, unannounced operating system shipping at some point in the future, a special version number of “9999” is used to mean “we don’t know what the actual number is just yet.”

Here’s the code:

var scores1 = [100, 91, 95, 98, 100]
let scores2 = [100, 98, 95, 91, 100]

if #available(iOS 9999, *) {
    let diff = scores2.difference(from: scores1)

    for change in diff {
        switch change {
        case .remove(let offset, _, _):
            scores1.remove(at: offset)
        case .insert(let offset, let element, _):
            scores1.insert(element, at: offset)
        }
    }

    print(scores1)
}

For more advanced animations, you can use the third value of the changes: associatedWith. So, rather than using .insert(let offset, let element, _) above you might write .insert(let offset, let element, let associatedWith) instead. This lets you track pairs of changes at the same time: moving an item two places down in your collection is a removal then an insertion, but the associatedWith value ties those two changes together so you treat it as a move instead.

Rather than applying changes by hand, you can apply the whole collection using a new applying() method, like this:

if #available(iOS 9999, *) {
    let diff = scores2.difference(from: scores1)
    let result = scores1.applying(diff) ?? []
}

Creating uninitialized arrays

SE-0245 introduces a new initializer for arrays that doesn’t pre-fill values with a default. This was previously available as a private API, which meant Xcode wouldn’t list it in its code completion but you could still use it if you wanted – and if you were happy to take the risk that it wouldn’t be withdrawn in the future!

To use the initializer, tell it the capacity you want, then provide a closure to fill in the values however you need. Your closure will be given an unsafe mutable buffer pointer where you can write your values, as well as an inout second parameter that lets you report back how many values you actually used.

For example, we could make an array of 10 random integers like this:

let randomNumbers = Array<Int>(unsafeUninitializedCapacity: 10) { buffer, initializedCount in
    for x in 0..<10 {
        buffer[x] = Int.random(in: 0...10)
    }

    initializedCount = 10
}

There are some rules here:

  1. You don’t need to use all the capacity you ask for, but you can’t go over capacity. So, if you ask for a capacity of 10 you can set initializedCount to 0 through 10, but not 11.
  2. If you don’t initialize elements that end up being in your array – for example if you set initializedCount to 5 but don’t actually provide values for elements 0 through 4 – then they are likely to be filled with random data. This is A Bad Idea.
  3. If you don’t set initializedCount it will be 0, so any data you assigned will be lost.

Now, we could have rewritten the above code using map(), like this:

let randomNumbers2 = (0...9).map { _ in Int.random(in: 0...10) }

That’s certainly easier to read, but it’s less efficient: it creates a range, creates a new empty array, sizes it up to the correct amount, loops over the range, and calls the closure once for each range item.

More to come!

Swift 5.1 is still under development, and although the final branching for Swift itself has passed there is still scope to see changes from some of the other associated projects.

Again, the big feature here is module stability, and I know the team are working hard on getting that right. They don’t announce shipping dates, although they have said that 5.1 “has a significantly shorter development” as a result of Swift 5.0 requiring “an unusual amount of focus and attention” – I’m guessing WWDC19, but obviously this isn’t something to be rushed for a particular date.

One thing that deserves special mention is that two of the changes listed here weren’t introduced as a result of Swift Evolution. Instead, the changes – “Warnings for ambiguous none cases” and “Matching optional enums against non-optionals” – were picked up as bugs and corrected quickly.

These are great quality of life improvements in Swift, but the reason I’m calling them out specially is because they were both fixed by a community contributor: Suyash Srijan. It’s fantastic to see development of Swift continue to grow beyond Apple, and Suyash’s work on those two highly visible features is helping make Swift easier and more consistent for everyone.

Best of all, the ambiguous enum bug was filed as a starter bug, which is one that the Swift team have picked out specifically to make it easier for folks to get started contributing. If you’d like to explore the current starter bugs for yourself, and perhaps even take a shot at fixing one, visit http://bit.ly/starterbugs.

UPGRADE YOUR SWIFT Hacking with Swift Live is a new iOS conference taking place in the UK this July, with all profits going to charity. Come and learn the major new APIs announced at WWDC19 with sessions and hands-on tutorials – click here to learn more!

 

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Was this page useful? Let me know!

Average rating: 4.1/5

Click here to visit the Hacking with Swift store >>