What's New In Swift 5.3?

Introduction

 
The Swift 5.3 version, extending language offers support across platforms like Windows and Linux distributions, has been one of the primary goals of this version, and Apple also has given a lot of focus on improving the overall language and its performance to boost SwiftUI and machine learning for iOS.
 
In this release following features are:
  • Multiple Trailing Closures
  • Multi-Pattern Catch Clauses
  • Synthesized Comparable Conformance for Enums
  • Enum Cases As Protocol Witnesses
  • self Isn't Explicitly Required Everywhere
  • A type-Based Program Entry point 
  • where Clauses on Contextually Generic Declarations
  • New Collection Operations for Noncontiguous Elements
  • A New Float16 Type 

Multiple Trailing Closures

 
The SE-0279 proposal brings a new syntax for trailing closures that lets you call multiple closures as parameters of a function in a more readable way. This is a bit of syntactic sugar that lets you "pop" the final argument to a function out of the parentheses when it's closure.
 
Instead, it allows you to append several labeled closures after the initial unlabeled closure. The following example is demonstrated below:
  1. //Old Multiple trailing closures  
  2. struct OldContentView: View {  
  3.     @State private var showOptions = false  
  4.   
  5.     var body: some View {  
  6.         Button(action: {  
  7.             //write button action here  
  8.         }) {  
  9.             Image(systemName: "star")  
  10.         }  
  11.     }  
  12. }  
  13.   
  14. //New Multiple trailing closures  
  15.   
  16. struct NewContentView: View {  
  17.     @State private var showOptions = false  
  18.   
  19.     var body: some View {  
  20.         Button {  
  21.             // write button action here  
  22.         } label: {  
  23.             Image(systemName: "star")  
  24.         }  
  25.     }  
  26. }  

Multi-Pattern Catch Clauses

 
SE-0276 introduced the ability to catch multiple error cases inside a single catch block, which allows us to remove some duplication in our error handling.  
 
Example
 
Catch clauses now allow the user to specify a comma-separated list of patterns with the ability to bind the variables with the catch body - like a switch statement.
  1. enum NetworkError: Error {  
  2.     case failure, timeout  
  3. }  
  4.   
  5. //Old style declaration  
  6. func networkCall(){  
  7.   do{  
  8.     try someNetworkCall()  
  9.   }catch NetworkError.timeout{  
  10.     print("timeout")  
  11.   }catch NetworkError.failure{  
  12.     print("failure")  
  13.   }  
  14. }  
  15.   
  16. //new style declaration  
  17. func networkCall(){  
  18.   do{  
  19.     try someNetworkCall()  
  20.   }catch NetworkError.failure, NetworkError.timeout{  
  21.     print("handle for both")  
  22.   }  
  23. }  

Synthesized Comparable Conformance for Enums

 
Until now, comparing two enums cases, you weren't straightforward. You'd have to conform to Comparable and write up a static fun < for determining if the raw value of case is lower than the other(or vice versa for >).
 
SE-0266 lets us opt into Comparable conformance for enums that either have no associated values or have associated values that are themselves Comparable. 
 
An example of an enum that sorts them:
  1. enum Brightness: Comparable {  
  2.     case low(Int)  
  3.     case medium  
  4.     case high  
  5. }  
  6.   
  7. ([.high, .low(1), .medium, .low(0)] as [Brightness]).sorted()  
  8. // [Brightness.low(0), Brightness.low(1), Brightness.medium, Brightness.high]  

Enum Cases As Protocol Witnesses

 
Swift had a very restrictive protocol witness matching model wherein writing enum cases with the same name and arguments as the protocol was not considered a match. We were forced to fall back on manual implementation instead. 
 
Example
  1. protocol DecodingError {  
  2.   static var fileCorrupted: Self { get }  
  3.   static func keyNotFound(_ key: String) -> Self  
  4. }  
  5.   
  6. enum JSONDecodingError: DecodingError {  
  7.   case _fileCorrupted  
  8.   case _keyNotFound(_ key: String)  
  9.   static var fileCorrupted: Self { return ._fileCorrupted }  
  10.   static func keyNotFound(_ key: String) -> Self { return ._keyNotFound(key) }  
  11. }  
SE-0280 has lifted this restriction so that enum cases can be protocol witnesses if they provide the same case names and arguments as the protocol requires.
 
Example
  1. protocol DecodingError {  
  2.   static var fileCorrupted: Self { get }  
  3.   static func keyNotFound(_ key: String) -> Self  
  4. }  
  5. enum JSONDecodingError: DecodingError {  
  6.   case fileCorrupted  
  7.   case keyNotFound(_ key: String)  
  8. }   

Self Isn't Explicitly Required Everywhere 

 
The SE-0269 proposal allows us to omit self in places where it's no longer necessary. Earlier, using self in closures was necessary when we were capturing values from the outside scope. Now when reference cycles are unlikely to occur, self would be implicitly present.
 
Example
  1. struct OldView: View {  
  2.   
  3.     var body: some View {  
  4.         Button(action: {  
  5.             self.sayHello()  
  6.         }) {  
  7.             Text("Press")  
  8.         }  
  9.     }  
  10.       
  11.     func sayHello(){}  
  12. }  
  13.   
  14. struct NewView: View {  
  15.   
  16.     var body: some View {  
  17.         Button {  
  18.             sayHello()  
  19.         } label: {  
  20.             Text("Press")  
  21.         }  
  22.     }  
  23.       
  24.     func sayHello(){}  
  25. }  

The type-Based Program Entry point

 
SE-0281 introduces a new @main attributed that lets us define entry points for our apps. You needn't invoke an AppDelegate.main() method manually anymore, as marking struct or class with the attributed ensures it's the starting point of your program.
 
Example
  1. @main  
  2. class AppDelegate: UIResponder, UIApplicationDelegate {  
  3. static func main() {  
  4.         print("App will launch & exit right away.")  
  5.     }  
  6. }  
One can assume that the older domain-specific provided attributes, @UIApplicationMain and @NSApplicationMain, would be deprecated in the future versions in favor of @main.
 
However, there are some conditions you should be aware of when using @main :
  • You may not use this attribute in an app that already has a main.swift file.
  • You may not have more than one @main attribute.
  • The @main attribute can be applied only to a base class - it will not be inherited by any subclasses.  

New Collection Operations for Noncontiguous Elements

 
SE-0270 introduces a new RangeSet type that can represent any number of positions in a collection. This might sound fairly simple, but it actually enables some highly optimized functionality that will prove useful in many programs.
 
Example
  1. var numbers = Array(1...15)  
  2.   
  3. // Find the indices of all the even numbers  
  4. let indicesOfEvens = numbers.subranges(where: { $0.isMultiple(of: 2) })  
  5.   
  6. // Find the indices of all multiples of 3  
  7. let multiplesofThree = numbers.subranges(where: { $0.isMultiple(of: 3) })  
  8.   
  9. let combinedIndexes = multiplesofThree.intersection(indicesOfEvens)  
  10. let sum = numbers[combinedIndexes].reduce(0, +)  
  11. //Sum of 6 + 12 = 18  
Using RangeSet, we can do a whole lot of computations and manipulations on collections. for example, by using the moveSubranges function, we can shift a range of indexes in an array. 
  1. let rangeOfEvens = numbers.moveSubranges(indicesOfEvens, to: numbers.startIndex)  
  2. // numbers == [2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15]  

Refined didSet Semantics

 
SE-0268 adjusts the way the didSet property observers work so that they are more efficient. Thus doesn't require a code change unless you were somehow relying on the previous buggy behavior, you will just get a small performance improvement for free.
 
Internally, this change makes Swift not retrieve the previous value when setting a new value in any instance where you weren't using the old value, and if you don't reference oldValue and don't have a willSet, Swift will change your data in-place.
  1. didSet {  
  2.     _ = oldValue  
  3. }   

A New Float16 Type

 
SE-0277 introduced a new half-precision floating-point type called Float16, which is commonly used in graphics programming and machine learning, this new floating-point type fits in alongside Swift's other similar types: 
 
Example
  1. let first: Float16 = 5  
  2. let second: Float32 = 11  
  3. let third: Float64 = 7  
  4. let fourth: Float80 = 13