Swift 5.3 — Sailing towards Performance and Quality
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.
References
Thank you so much Meet Gupta for motivating me and for all the help.