Swift 5.1 - what's new?

Original author: Paul Hudson
  • Transfer


Swift 5.0 became available with the release of Xcode 10.2, but work on the next version continues and there is already news of what you can expect in it.

A key feature of Swift 5.1 is module stability , which allows us to use third-party libraries without worrying about which version of the Swift compiler they were created with. It looks like the ABI stability we got in Swift 5.0, but there is a slight difference: ABI stability resolves differences in Swift versions at runtime, and module stability at compile time.

In addition to this important innovation, we will get several important improvements in Swift, and in this article we will go over them with examples so that you can see them in action.

Universal Self


SE-0068 extends the use of Self , so that it refers to the type containing it within classes, structures, and enumerations. This is usually useful for dynamic types when it is necessary to determine the exact type of something at runtime.

As an example, consider the following code:

class NetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }
    func printDebugData() {
        print("Maximum network requests: \(NetworkManager.maximumActiveRequests).")
    }
}

Here we define the static property maximumActiveRequests inside the NetworkManager class and add the printDebugData () method to print this property. Everything is fine here, but only until we decide to inherit from NetworkManager :

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

In this heir, we change the maximumActiveRequests property so that now it becomes equal to one, but if we call printDebugData () , it will infer the value from the parent class:

let manager = ThrottledNetworkManager()
manager.printDebugData()

Here we should get 1 instead of 4, and here comes to the rescue SE-0068: we can use Self (with the capital 'S') to refer to the current type. So now we can rewrite the printDebugData () method of the parent class like this:

class ImprovedNetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }
    func printDebugData() {
        print("Maximum network requests: \(Self.maximumActiveRequests).")
    }
}

That is, Self works in the same way that it worked in protocols in earlier versions of Swift.

Warnings for ambiguity none


Optionals in Swift are implemented as an enumeration with two options: some and none . This can lead to confusion if we create our own enumeration that has a none option and wrap it in optional . For instance:

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

When using non-optional everything is clean:

let border1: BorderStyle = .none
print(border1)

This will output “none”. But if we use optional for this enumeration, then we will encounter a problem:

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

There will be printed nil , since Swift believes that .none means that the optional items , although in fact it is optional with the value BorderStyle.none.
In Swift 5.1, in the event of such ambiguity, a warning is displayed:
“Assuming you mean 'Optional.none'; did you mean 'BorderStyle.none' instead? ”
Thus, the developer will be informed that with his code not everything can be smooth.

Matching optional and non-optional enumerations


Swift is smart enough to understand the switch / case construct when combining optional / non-optional text and integer values, but not in the case of enumerations.

Now in Swift 5.1 we can use switch / case to match the enumeration-optional and non-optional options:

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 map optional enumerations to non-optional options, and “Build is starting ...” will be displayed here.

Compare ordered collections


SE-0240 introduced the ability to calculate differences between ordered collections, as well as apply the resulting comparison result to collections. This may be of interest to developers who have complex collections in a tableview and need to add or remove many elements using animation.

The basic principle is simple - Swift 5.1 provides a new method difference (from :) , which determines the differences between two ordered collections - which elements to add and which to remove. This applies to any ordered collections that contain items that comply with the Equatable protocol .

To demonstrate this, we will create two arrays of values, calculate the differences of one from the other, and then go through the list of differences and apply them to make the two collections the same.

Note: since Swift is now distributed as part of Apple’s operating systems, new language tools should be used with the #available check to make sure that the code runs on an OS that supports the required functionality. For functionality running on unknown, unannounced OSs that may be released in the future, a special version number is used, “9999,” which means: “We do not yet know the correct version number.”

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 a more advanced animation, we can use the third parameter in the resulting list of differences: associatedWith . Thus, instead of .insert (let offset, let element, _), we can write .insert (let offset, let element, let associatedWith ). This gives us the ability to simultaneously track pairs of changes: moving an element in the collection two positions down is deleting the element and then adding it, and associatedWith “binds” these two changes together and allows you to consider this movement.

Instead of applying the differences manually, one by one, you can apply them in one fell swoop using the new applying () method :

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

Creating Uninitialized Arrays


SE-0245 introduced a new initializer for arrays that does not fill it with default values. It was available earlier as a private API, which meant that Xcode did not prompt it in code completion, but you could use it if you needed it and you understood the risk that this functionality might not be in the future.

To use the initializer, set the size of the array, then pass the closure that fills the array with values. A closure will receive an unsafe pointer to a mutable buffer, as well as a second parameter in which you indicate how many values ​​you actually use.

For example, we can create an array of 10 random integers like this:

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

There are several rules:

  1. You do not need to use the entire volume that you requested, but you cannot exceed it. That is, if you set the size of the array to 10, then you can set initializedCount in the range from 0 to 10, but not 11.
  2. if you did not initialize the elements used in the array, for example, you set initializedCount to 5, but did not provide real values ​​to elements 0 through 4, then they will most likely receive random values. As you know, this is a bad option.
  3. If you do not set initializedCount , then it will be equal to 0 and all the data that you assigned will be lost.

Yes, we could well rewrite the code using map () :

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

This is obviously more readable, but not so effective: we create a range, then a new empty array, assign it a size, and “go through” the entire range, applying a closure to each element.

Conclusion


Swift 5.1 is still under development, and although the final branch for Swift itself has passed, changes from some other related projects are still visible.

So, the most important change is module stability , and it is known that the development team is working hard on this. They did not give an exact release date, although they said that Swift 5.1 had a significantly shorter development time compared to Swift 5.0, which required an extraordinary concentration of energy and attention. We can assume access to WWDC19, but it is obvious that this is not the case when you need to rush to a certain date.

Another point that deserves attention. Two changes in this list (“Warnings in case of ambiguity of the none option” and “Matching optional and non-optional enumerations”) were not the result of the Swift evolution, but were recognized as bugs and adjusted.

Also popular now: