Swift 5.2 was released with Xcode version 13.4 and includes a reduced code size and memory usage, a new diagnostic architecture that will help you understand and resolve errors faster.
This release focuses on improving the developer experience.
A few additions to the language have been added that provide a new capability for building expressive APIs.
Swift 5.2 implements the following language proposals from the Swift Evolution process:
- SE-0249 Key Path Expressions as Functions
- SE-0253 Callable values of user-defined nominal types
Key Path Expressions as Functions
The Evolution proposal describes this as being able to use "\Root.value" wherever functions of (Root) -> Value are allowed, but what it means is that if previously you sent a Car into a method and got back its license plate, you can now Car.licensePlate instead.
Let's take an example. A User type struct class defines four properties :
- struct User {
- let name: String
- let age: Int
- let bestFriend: String?
- var canVote: Bool {
- age >= 18
- }
- }
Let's create some instance of above struct and put them into an array, like below :
- let ayush = User(name: "Ayush Singh", age: 18, bestFriend: "Aman")
- let aman = User(name: "Aman Pandey", age: 19, bestFriend: nil)
- let shanky = User(name: "Shanky", age: 17, bestFriend: "Panky")
- let users = [ayush, aman, shanky]
Now In the above example if you want to get an array of all the users' names, you can do so by using a key path like below :
- let userNames = users.map(\.name)
- print(userNames)
Previously you would have had write a closure to retrieve the name like below :
- let oldUserNames = users.map { $0.name }
This same approach works anywhere where previously you would have received a value and passed back one of its properties, you can now use a key path instead.
For example, this will return all users who can vote :
- let voters = users.filter(\.canVote)
One more example that will return the best friends for all users who have one best friend.
- let bestFriends = users.compactMap(\.bestFriend)
Callable values of user-defined nominal types
SE-0253 introduces statically callable values to the swift, In which you can now call a value directly if its type implements a method name callAsFunction(). You don't need to conform to any special protocol to make this behavior work, you just need to add that method to your type.
Example
- struct Dice {
- var lowerBound: Int
- var upperBound: Int
-
- func callAsFunction() -> Int {
- (lowerBound...upperBound).randomElement()!
- }
- }
-
- let d6 = Dice(lowerBound: 1, upperBound: 6)
- let roll1 = d6()
- print(roll1)
Swift automatically adapts your call sites based on how callAsFunctons() is defined. for example, you can add as many parameters as you want, you can control the return value, and you can even mark methods as mutating if needed.
callAsFunction() supports both throws and rethrows, and you can even define multiple callAsFunction() methods on a single type - Swift will choose the correct one depending on the call site, just like regular overloading.
Subscripts can now declare default arguments
When adding custom subscripts to a type, you can now use default arguments for any of the parameters. For example, if we had an EmployeesList struct with a custom subscript to read senior engineers from the employeeList, we could add a default parameter to send back if someone tries to read an index outside of the array's bounds,
Example
- struct EmployeesList {
- var seniorEngineers: [String]
-
- subscript(index: Int, default default: String = "Unknown") -> String {
- if index >= 0 && index < seniorEngineers.count {
- return seniorEngineers[index]
- } else {
- return `default`
- }
- }
- }
-
- let seniorEngineer = EmployeesList(seniorEngineers: ["James", "Scott", "Rosa", "Terry"])
- print(seniorEngineer[0])
- print(seniorEngineer[5])
That will print "James" then "Unknown", with the latter being caused because there are no seniorEngineers at index 5. Note that you do need to write your parameter labels twice if you want then to be used because subscripts don't use parameter labels otherwise.
So, because I use default in my subscript, I can use a custom value like the below example :
Swift 5.2 introduced a new diagnostic architecture that aims to improve the quality and precision of error messages issued by Xcode when you make a coding error. This is particularly apparent when working with SwiftUI, where Swift would often produce a false-positive error message.
In the above example attempts to bind a TextField view to an integer @State property, which is invalid. In Swift 5.1 this caused an error for the frame() modifier saying Int is not convertible to CgFloat, but in Swift 5.2 and later this correctly identifies the error is the $name binding: can not convert the value of type Binding to expected argument type Binding.
- Faster completion by eliminating unnecessary type checking. For large files, it can speed-up code completion by 1.2x to 1.6x, compared to Xcode 11.3.1, depending on the completion position.
- Now it can supply names of implicit members for incomplete dictionary literals and incomplete ternary expressions.
- Easier to read types when they appear in the results. Using opaque result types. when possible and preserving type aliases. Stopped printing parent types if not necessary.
Improved Build Algorithm
The Swift compiler supports two modes of operation,
- Whole Module (Typically used for Release builds)
- Incremental (typically used for debug build)
In Xcode, these can be seen in the build settings for a Swift project
The two modes have balanced in compilation speed and amount of code optimization performed.
- Incremental builds are great during development where not every file in the project need to be recompiled, and maximum optimization is not critical.
- Whole module optimization gives the compiler a more complete view of the entire code base and therefore a greater ability to optimize.