What’s New in Swift 5.2 [FREE]
source link: https://www.raywenderlich.com/9224115-what-s-new-in-swift-5-2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Swift 5.2 is now available as part of Xcode 11.4. This article presents an overview of the changes you’ll see in this latest version.
Overall, Swift 5.2 is a minor release which is not necessarily a bad thing. It does bring many tweaks and minor improvements that will help the workflow of Swift developers. In this release, you’ll find:
- Better diagnostics with more helpful error messaging, especially for SwiftUI.
- New features that will simplify certain tasks.
- Major bug fixes.
In many of the sections below, you’ll see references to the Swift Evolution proposals, like SE-0253, or the Swift bug reports, like SR-11298. You can explore these in greater depth using the links provided.
Also, follow along with the Playgrounds created for this article. Use the Download Materials button at the top or bottom to download them.
To start, you’ll explore the highest-profile feature: improvements to the error messages.
Improved Diagnostics and Error Messages
Do you always write perfect code on the first try? If not, you’ll love the improvements to the diagnostics engine in Swift 5.2! When you have an error in your code, the compiler gives you a more accurate description of the error and its location.
To be fair, the Swift compiler already did a good job reporting most errors in your code. The code below:
var str = "10" let total = str + 5
…produced a clear error message:
The error message told you that you cannot use the ‘+’ operator to join a String
and an Int
. Depending on your intent, it also pointed you toward the action to fix the problem. If the goal was to add 5
to the number represented by the string, then you could fix the error like this:
var str = "10" let total = Double(str)! + 5
At other times, however, the error messages weren’t all that helpful. This was especially true with errors related to type checking. Swift 5.2 addressed this issue by focusing on improvements to the type checker.
Easier Troubleshooting
Consider the following code for SwiftUI:
struct SquareView : View { @State var angle = 0.0 var body: some View { VStack { TextField("Angle:", text: $angle) Rectangle() .rotation(Angle(degrees: angle)) .frame(width: 100.0, height: 100.0) } } }
Prior to Xcode 11.4, the error message you would have seen was this:
An ambiguous error in SwiftUI from older Swift versions
Not very helpful, eh? It was common to see unclear error messages such as this when working with SwiftUI. This made it that much harder to learn to use SwiftUI. Finding even the simplest typos became a process of tediously re-reading, removing or commenting out code to isolate the actual error.
Look again at the code block. Can you spot the actual problem? As a hint, it has nothing to do with converting a Double
to a CGFloat
!
Now open swiftui.playground in the starter folder. You’ll see that the compiler gives you a much more useful and actionable error message:
The improved error from Swift 5.2 and above
The error message points you to the correct line with the error and tells you the problem: You’re passing a binding to Double
to a method that expects a binding to String
.
Now that you know what to do to fix this error, replace the line indicated with this:
TextField("Angle", value: $angle, formatter: NumberFormatter.decimalFormatter)
Not Just SwiftUI
While you’ll notice better error messaging most often in SwiftUI, you’ll also see improvements in other Swift code. Open swift52.playground in the final project and you’ll find this code commented out:
let x: [Int] = [1, 2, 3, 4] let w: UInt = 4 let filtered = x.filter { ($0 + w) > 42 }
This code attempts to add an Int
to a UInt
without a cast and will not compile. Prior to Swift 5.2, the compiler displayed the following error:
error: binary operator '+' cannot be applied to operands of type 'Int' and 'UInt'
Now uncomment the code and you’ll see a more precise and helpful error message:
error: new-features.playground:34:22: error: cannot convert value of type 'UInt' to expected argument type 'Int' _ = x.filter { ($0 + y) > 42 } ^ Int( )
Note : For a more detailed description of the improvements to the diagnostic engine, check out this blog article: New Diagnostic Architecture Overview .
Syntactic Sugar Additions
Swift already has quite a bit of syntactic sugar built into the language. Swift 5.2 adds to this by bringing two new features that certain developers will find very handy: Calling types as functions and using key path expressions as functions.
Note : Syntactic sugar changes the syntax of the language to make it easier to understand or more concise.
Calling Types as Functions
This new feature introduces statically callable values to Swift. But what does that mean? Well, it means that you can call classes or other structures as if they were functions.
To implement this feature in your code, you add a method named callAsFunction(_:)
to the type. That’s it! There’s no step two.
With this addition, you can now call the value as a function. For example, take this type that represents a quadratic equation — a common mathematical function in the form of ax^2 + bx + c :
struct Quadratic { var a: Double var b: Double var c: Double func callAsFunction(_ x: Double) -> Double { a * pow(x, 2) + b * x + c } }
You define your type as with any other value:
let f = Quadratic(a: 4, b: 4, c: 3)
Note that adding callAsFunction(_:)
doesn’t prevent you from using the default init()
method.
The choice of a mathematical type for this example is not a coincidence. Using syntax similar to mathematical notation provides the primary motivation for this feature.
let y = f(5)
As with any method, you can have multiple overrides for callAsFunction(_:)
in your type. If you wanted to also calculate values for an array of Double
s and return the result as a new array, you could add a second implementation of callAsFunction(_:)
like this:
func callAsFunction(_ xs: [Double]) -> [Double] { xs.map { callAsFunction($0) } }
This code takes an array of Double
s and uses map()
and the previous implementation of callAsFunction(_:)
to produce an array with the results. Swift determines the appropriate method to call as it would for any other overridden function.
let z = f([5, 7, 9])
Cleaner Syntax
You could achieve the same result of this feature by adding a traditional method on this type. But the new syntax is cleaner, especially where there’s a single, obvious action for a value. In this example, the obvious action for the Quadratic
type would be calculating the value of the equation for a given x
. Similarly, a Parser
will most often parse input as its primary function.
Machine Learning Application
This feature seems particularly focused on machine learning. You see this in the original proposal as it specifically mentions cleaning up the syntax for neural network applications. And it’s similar to Python and cross-compatible. All of this makes Swift even better suited for ML developers.
Note : For more information about this proposal, see this GitHub page: SE-0253: Callable values of user-defined nominal types
Key Path Expressions as Functions
With Swift 5.2, the language now allows \Root.value
key path expressions wherever you could already use (Root) -> Value
functions.
In code terms, where you could write:
orders.map { $0.email }
…you can now also write:
orders.map(\.email)
The current implementation limits this feature to key path literal expressions. You cannot currently write the following, though the discussion for this feature suggests future implementation:
let kp = \.email users.map(kp)
However, you can accomplish something similar with different syntax:
let asEmail: (Order) -> String = \Order.email orders.map(asEmail)
The first two features bring better functional abilities to Swift. You can now use functions in places that previously required blocks or closures. Since you now can pass a key path as a function, you can also pass a key path to a value that implements the new callAsFunction()
.
Note : For more information about this proposal, see this GitHub page: SE-0249: Key Path Expressions as Functions
Subscripts With Default Arguments
With Swift 5.2, you can now declare default arguments for custom subscripts when adding them for a type. For example, here’s a simple type that uses subscripts for multiplication:
struct Multiplier { subscript(x: Int, y: Int = 1) -> Int { x * y } } let multiplier = Multiplier()
This addition allows you to write code that specifies any number of available subscripts. Swift uses the provided default for any subscript not specified:
multiplier[2, 3] multiplier[4]
Note : For more information about change, see this article at Swift.org: SR-6118: Mechanism to hand through #file/#line in subscripts
Major Bug Fixes
While new features get most of the attention in new releases, bug fixes are also critical. You’ll learn about these next.
Lazy Filters are Called in Order
When chaining calls to filter(_:) on a lazy sequence or collection, the filtering predicates are now called in the same order as eager filters. This bug fix is the one most likely to break existing code. For most collections, Swift calls filtering predicates in order. So this code:
let array = ["1", "2", "3"] let filtered = array .filter { _ in print("A") return true } .filter { _ in print("B") return true } _ = Array(filtered)
…will print:
A A A B B B
Before Swift 5.2, evaluating a lazy sequence or collection evaluated in the reverse order. Take this example:
let lazyFiltered = array.lazy .filter { _ in print("A") return true } .filter { _ in print("B") return true } _ = Array(lazyFiltered)
You would expect it to print:
A B A B A B
…but it actually prints:
B A B A B A
When compiled with Swift 5.2, the results print in the expected order. If you’ve written code that counts on the reversed execution, you’ll need to update your code to reflect the fixed behavior.
Note : For more information about this bug fix, see this article at Swift.org: SR-11841: Lazy filter runs in unexpected order
The remaining bug fixes in Swift 5.2 have less of an impact on existing code but are worth noting. You’ll want to know about these changes if you have worked around these issues in the past.
Default Values From Outer Scopes
The compiler now supports local functions whose default arguments capture values from outer scopes. This allows code such as this:
func outer(x: Int) -> (Int, Int) { func inner(y: Int = x) -> Int { return y } return (inner(), inner(y: 0)) }
Before Swift 5.2, the code above would not work.
Note : For more information about this bug fix, see this article at Swift.org: SR-2189: Nested function with local default value crashes
Warning When Passing Dangling Pointers
The compiler will now show a warning when you try to pass a temporary pointer argument that attempts to outlive the call. This would include code such as:
func generatePointer() { var number: Int8 = 0 let pointer = UnsafePointer(&number) }
This will result in a warning stating:
warning: initialization of 'UnsafePointer<Int8>' results in a dangling pointer<int8>
Note : Code that leaves a dangling pointer almost always indicates an error. A future version of the language may indicate this instead of just giving a warning.
For more information about this bug fix, see this article at Swift.org: SR-2790: Reject UnsafePointer initialization via implicit pointer conversion
Overridden Methods Can’t Use Incorrect Generics
Previously, you could write override a method with a generic signature incompatible with the base method’s generic signature. For example, you could write this:
protocol P { } class Base { func doWork<T>(input: T) { } } class Derived: Base { override func doWork <T: P>(input: T) { } }
In Swift 5.2, this code no longer compiles and will now throw an error.
Note : For more information about this bug fix, see this article at Swift.org: SR-4206: Override checking does not properly enforce requirements
Class-Constrained Protocol Extensions
A class-constrained protocol extension, where the extended protocol does not impose a class constraint, will now infer the constraint implicitly. Consider the following code:
protocol Foo {} class Bar: Foo { var someProperty: Int = 0 } extension Foo where Self: Bar { var anotherProperty: Int { get { return someProperty } set { someProperty = newValue } } }
Here Foo
does not impose a class
constraint. But the compiler
infers this due to the Self: Bar
constraint on Foo
. This results in the setter
becoming implicitly non-mutating, just as if Foo
had a class constraint.
Note : For more information about this bug fix, see this article at Swift.org: SR-11298: Writable property declaration in a conditional-conforming protocol extension has incorrect mutability
Disambiguate Functions with Named Parameters
You can now use the as
operator to disambiguate a call to a function with argument labels. Before, you could only do this for functions without label arguments. Consider these two functions:
func print(x: Int) { print("Int \(x)") } func print(x: UInt) { print("UInt \(x)") }
Now you can differentiate the two functions using as
with the following syntax:
(print as (Int) -> Void)(5) // Prints Int 5 (print as (UInt) -> Void)(5) // Prints UInt 5
There is one side effect to this change: You can no longer use a generic typealias
to preserve the argument labels of a function reference using the as
operator. An example of this would be:
typealias Magic<T> = T (print as Magic)(x: 5)
This code will now result in a compiler error:
Extraneous argument label 'x:' in call
Instead, you must eliminate the argument from the call:
(print as Magic)(5)
This prints Int 5 like the first call above.
Note : For more information about this bug fix, see this article at Swift.org: SR-11429: Don’t look through CoerceExprs in markDirectCallee
Where to Go From Here
If you haven’t already, click the Download Materials button at the top or bottom of this article. Open these Playground files to explore many of the new changes and features for Swift 5.2.
While Swift 5.2 is not a major update, it does bring welcome changes and additions to the language. Nearly all developers will benefit from the improvements in diagnostics and error messaging. If they use machine learning in their projects, they’ll also appreciate the new type and key path features. And developers working with custom collections will welcome the addition of default subscript types.
If you would like to learn more about the Swift Evolution process, check out these links:
- GitHub change log for Swift . This document gives a short summary of the changes for each release.
- GitHub listing of Swift Evolution proposals . This document provides links to the GitHub page for each proposal. Use this to read the full description of the reasons and thought processes behind a given change.
- Swift.org bugs and issues dashboard . Use this site to view the full description, discussion and activity for the reported bugs and issues in Swift.
What are your thoughts about the new changes and features in Swift 5.2? Let us know in the forum discussion below!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK