New features in Swift 4.2

Yesterday, Apple officially released the latest version of Swift programming language - Swift 4.2. It is a major release with a lot of improvements that include faster build times and features aimed towards removing boilerplate code and improving efficiency. It is also a significant step towards ABI stability planned for Swift 5.

There were many proposals that were accepted in this release. In this blog post, I will go through the ones that I find interesting.

Hashable enhancements

To be able to use a custom type in Set or as a key in Dictionary, it has to conform to Hashable protocol. In Swift 4.1, Hashable protocol looked like this:

protocol Hashable {
    var hashValue: Int { get }
}

Each custom type that implements Hashable has to provide a hashValue. Calculating the hashValue is not as simple as it seems. If the algorithm is not good enough, the cost is pretty high, because it impacts the performance of Dictionary and Set. That is why Hashable protocol was redesigned in Swift 4.2:

protocol Hashable {
    func hash(into hasher: inout Hasher)
}

To implement the new Hashable protocol, we have to call the hash function on the conforming type’s properties that we want to use in hash calculation and pass in the hasher as an argument. As an example, we can implement it on Person struct:

struct Person: Hashable {
    let name: String
    let age: Int

    func hash(into hasher: inout Hasher) {
        name.hash(into: &hasher)
        age.hash(into: &hasher)
    }
}

Note that there is also a generic Hasher.combine<H>(_:) method which is completely equivalent to Hasher.hash(into:) method and can be used in the same way.


Diagnostic directives - #warning and #error

An interesting feature implemented in this release are #warning and #error compiler diagnostic directives. As their names already say, #warning will cause Xcode to emit a warning when building code and #error will cause a compile error. #warning is very useful for marking code that is unfinished and it is a much more robust solution than marking the code with FIXME or TODO. #error can be used, for example, to notify the user that a library is not available on certain platforms:

#if LIB_VERSION < 3 && os(iOS)
#error("MyLib versions < 3 are not supported on iOS")
#endif


CaseIterable protocol

Before Swift 4.2, there was no standard way of listing all cases in enums. What one could do is create an allCases static property that returns a collection of all enum cases:

enum ConnectionState {
    case connecting
    case connected
    case disconnected

    static var allCases: ConnectionState = [.connecting, .connected, .disconnected]
}

This solution, however, is not optimal, because if we later add another case in the enum we have to add that case to allCases property.
This is where CaseIterable protocol comes into play. Our enum only has to conform to the protocol and the allCases property will be generated for us at compile time. Cases are ordered in order of their declaration.

enum ConnectionState: CaseIterable {
    case connecting
    case connected
    case disconnected
}

Note that compiler synthesis only works for enums without associated values.

Upgrading self from a weak to strong reference

One common pattern when working with closures in Swift is capturing self weakly to avoid retain cycles. The only mechanism for upgrading weak self to a strong reference is to unwrap it using if let or guard let statements. The problem is that we have to use a variable with an arbitrary name like strongSelf because we can’t use self for variable’s name. This introduces a lot of inconsistencies in the codebase.
Now, as of Swift 4.2, we can use self for naming the variable used for unwrapping like this:

DispatchQueue.main.async { [weak self] in
    guard let self = self else { return }
}

Conclusion

I’ve covered some of the features that were introduced in Swift 4.2 that were interesting to me. There are a lot of changes that I didn’t cover. You can read more about them on Apple’s official Swift blog.