Swift 5.3 — Sailing towards Performance and Quality

Sakshi Chauhan
The Startup
Published in
9 min readMay 17, 2020

--

The last stable release of SwiftLang was 5.2.3 which came in April 2020 and 5.3 is ready to make its apperance as it is in the stage of development and the release branch has already been created. The least to expect in the WWDC announcements this year is that Xcode 12 will include Swift 5.3 🤯. Let’s dive in to see what’s in this pre-release package 🚀

Within these efforts, the language is expanding the availability and support of Swift for Windows and additional Linux distributions. Swift now offers downloadable toolchain and docker images for various Linux distributions.

But will the Windows or Linux developers actually switch to Swift for development over .NET languages? 🤔

Support of SwiftUI on Windows can surely fuel the adoption of Swift cross-platform packages. Let’s see if Apple has any plans around SwiftUI to give developers another reason to migrate to Swift with this amazing declarative way of creating UI.

More power to Swift Package Manager

Resources

Until now, libraries that have embedded assets were not able to add SwiftPM support. Apple has finally accepted the proposal of adding bundle resource files alongside code in Swift Package Manager. With the implementation of Swift Evolution SE-0271, we now have a consistent way of accessing the resources (images, data files, and other resources needed at runtime) from the source code in the package. 😎

Localized Resources — SE-0278

How amazing would it be if we could also ship localized resources as part of SwiftPM 🧐?

Good news, we can now define localized versions of resources in the SwiftPM manifest and have them automatically accessible at runtime using the same Foundation Bundle APIs.

Binary Dependencies

Prior to Swift 5.1, the Swift compiler itself did not expose all the features
(like ABI compatibility) required for building a workable solution. Since those features are present now, it makes sense to re-evaluate the role of binary packages.

Implementation of SE-0272 has made it possible to have a binary target dependency at a given path or location and you can use the binary as a product in a library or executable.

We have been using existing package managers like CocoaPod to rely on closed source dependencies like Firebase, Google Analytics, and many more. Guess what! We can now easily integrate closed libraries with SwiftPM that do not deliver source code.

It has also paved a way for existing code base which would like to integrate “simply” with SwiftPM, but require more complicated build processes.

Conditional Target Dependencies

Well, we are aware that the packages which span over multiple platforms may need to depend on different libraries depending on the platform. Here comes yet another nice addition with the development of SE-0273, we can now use dependencies based on the target platform.

Language Feature Updates

Multiple Trailing Closures

It’s not hard to guess why trailing closures have gained so much popularity 😄. We know the call site is easier to read, it is more concise and less nested, without loss of clarity.

However, the restriction of trailing closure syntax to only the last closure has limited its applicability. Implementation of SE-0279 allows additional labeled closures to follow the initial unlabelled closure.

Two key points to keep in mind:

  • The first trailing closure drops its argument label.
  • Subsequent trailing closures require argument labels.

Before Swift 5.3 — Single trailing closure

UIView.animate(withDuration: 0.5, animations: {
self.view.alpha = 0.5
}) { _ in
self
.view.backgroundColor = UIColor.red
}

Swift 5.3 — Multiple trailing closure

UIView.animate(withDuration: 0.5) {
self.view.alpha = 0.5
} completion: { _ in
self
.view.backgroundColor = UIColor.red
}

Well, so much efforts, just to remove a few braces 🤔. No wonder inclusion of this feature is highly debatable 🧐

Synthesized Comparable Conformance for enum types

With Swift 4.1, the compiler had a way to automatically synthesize conformance to Equatable and Hashable. This led to a reduction of boilerplate code in the scenarios wherever correct implementation is possible.

Their sibling Comparable was left out at that time due to the fact that it was complex to get the comparison order. Well, not anymore 😄. With SE-0266, Swift 5.3 brings opt-in synthesized Comparable conformances for enum types without raw or associated values. Example from the GitHub page,

enum Membership: Comparable {
case premium(Int)
case preferred
case general
}
let array: [Membership] = [.premium(1), .preferred, .premium(0), .general]let sortedArray = array.sorted()
//[.premium(0), .premium(1), .preferred, .general]

The older version would give an error like this Type ‘Membership’ does not conform to protocol ‘Comparable but in Swift 5.3, it works like a charm 🌟

We can, of course, have our own implementation like before if needed.

Enum Cases as Protocol Witnesses

Enums have always made our lives easier if you know how to use them 😉wisely. Swift 5.3 adds more to its sweetness.

This implementation in SE-0280 aims to lift an existing restriction that the enum cases cannot participate in protocol witness matching. The Github readme has an awesome must-look example to understand this.

Now, the compiler allows a static protocol requirement to be witnessed by an enum case, under the following rules:

A static, get-only protocol requirement having an enum type or Self type can be witnessed by an enum case with no associated values.

A static function requirement with arguments and returning an enum type or Self type can be witnessed by an enum case with associated values having the same argument list as the function's.

Type-Based Program Entry Points

With the implementation of SE-0281, users can now use the @main attribute on a single type instead of writing top-level code.

The Swift compiler will recognize a type annotated with the @main attribute for providing the entry point for a program. Types marked with @main have a single implicit requirement: declaring a static main() method. This main() method will typically be provided by libraries or frameworks so that the author of a Swift program will only need to use the @main attribute to indicate the correct starting point.

// In a framework:
public protocol ApplicationRoot {
// ...
}
extension ApplicationRoot {
public static func main() {
// ...
}
}

// In MyProgram.swift:
@main
struct MyProgram: ApplicationRoot {
// ...
}

Multi-Pattern Catch Clauses

Currently, each catch clause in a do-catch statement can only contain at-most single pattern and where clause.

Because of the above restrictions, a developer ends up either nesting the switch inside a catch clause, defeating the purpose of supporting pattern matching in catch clauses, or ends up splitting the code into multiple catch clauses, thus duplicating the body, which is undesirable. With SE-0276, Swift 5.3 adds another capability of having multiple catch cases at once 🎉.

do {
// do something
} catch TaskError.someRecoverableError { // OK
recover()
} catch TaskError.someFailure(let msg),
TaskError.anotherFailure(let msg) { // Also Allowed
showMessage(msg)
}

Float16 — SE-0277

The last decade has seen a dramatic increase in the use of floating-point types smaller than Float(32-bit).

As stated in the proposal, the addition of this type can be used widely in various use-cases on mobile GPUs for computation, as a pixel format for HDR images, and in ML applications for compressed format for weights.

Thus adding Float16 seems right ✅. With the advancements happening, we can expect some more Math functions coming soon 😃

Implicit “self”

Tired of writing self in escaping closure blocks? SE-0269 brings a surprise of implicit self to you. This can help your laziness to persist 😆. It is quite useful in situations where the developer has already made their intent explicit, or where strong reference cycles are otherwise unlikely to occur. It might be a bit dangerous for devs who rely on the Poka-Yoke mechanism to safeguard the retain-cycles🧐.

Refine didSet Semantics

With the current implementation of didSet observer, the getter of property gets called to fetch the oldValue whether we use it or not in the body of the observer. What's wrong with that…💁🏻‍♀️But if we look closely, it is doing some redundant work by allocating storage and loading a value that isn’t used. And that…could also be really expensive 🤓.

SE-0268 brings down this redundancy and uses a lazy approach. So the property’s getter is no longer called if we do not refer to the oldValue within the body of the didSet 🥳.

Collection Operations on Non-Contiguous Elements 😯

Range<Index> is used to perform various operations on collections with consecutive positions. But what if we can use it with discontiguous positions in an arbitrary collection?

SE-0270 adds a RangeSet type to represent multiple, noncontiguous ranges, as well as a variety of collection operations for creating and working with range sets.

var numbers = Array(1...15)

// Find the indices of all the even numbers
let indicesOfEvens = numbers.subranges(where: { $0.isMultiple(of: 2) })

// Perform an operation with just the even numbers
let sumOfEvens = numbers[indicesOfEvens].reduce(0, +)
// sumOfEvens == 56

// You can gather the even numbers at the beginning
let rangeOfEvens = numbers.moveSubranges(indicesOfEvens, to: numbers.startIndex)
// numbers == [2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15]
// numbers[rangeOfEvens] == [2, 4, 6, 8, 10, 12, 14]

“where” clauses on contextually generic declarations

SE-0267 lifts the restriction on attaching where clauses to member declarations that can reference only outer generic parameters.

This improvement only covers member declarations that already support a generic parameter list i.e. properties and unsupported constraints on protocol requirements are out of scope.

protocol P {
// error: Instance method requirement 'foo(arg:)' cannot add constraint 'Self: Equatable' on 'Self'
func foo() where Self: Equatable
}

class C {
// error: type 'Self' in conformance requirement does not refer to a generic parameter or associated type
func foo() where Self: Equatable
}

Whereas placing where clause on an extension member rather than the extension itself becomes possible:

extension P {
func bar() where Self: Equatable { ... }
}

String Initializer added with Access to Uninitialized Storage

There are situations where we need a temporary buffer to be allocated and copied into. For example, bridging NSString to String, which currently uses standard library internals to get good performance when using CFStringGetBytes.

SE-0263 adds a new initializer unsafeUninitializedCapacity. As explained in ReadMe “It takes a closure that operates on an UnsafeMutableBufferPointer referencing the uninitialized contents of the newly created String's storage and returns a count of initialized elements or 0.”

This helps in reducing the overhead of heap allocation that is needed for this temporary usage.

Wondering what are unsafe pointers. Here is an amazing article to know everything about unsafe pointers in swift.

What else

Can’t wait to get your hands dirty with Swift 5.3? Go ahead with these snapshots provided by swift.org to experiment with new features and contribute to the open-source.

To Windows or Linux developers

References

Thank you so much Meet Gupta for motivating me and for all the help.

--

--