New Features with Swift 5.4

Swift 5.4 brings some major build improvements, including better code completion and great accelerations for incremental compilation. It also adds some important new features and improvements, so let's examine them here ...

Improved implicit member syntax

The SE-0287 improves Swift's ability to use implicit member expressions, so you can create chains of them instead of just getting support for a single static member.

Swift has always been capable of using implicit member syntax for simple expressions, for example, if you want to color text in SwiftUI, you can Color.red instead .red you can use :

struct ContentView1: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(.red)
}
}

That didn't work withmore complex expressions than Swift 5.4. For example, if you want your red color to be a little transparent, you need to type:

struct ContentView2: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(Color.red.opacity(0.5))
}
}

Starting with Swift 5.4, the compiler can understand multiple-chain members, so ColorThe type can be removed:

struct ContentView3: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(.red.opacity(0.5))
}
}

Multiple variadic parameters functions

SE-0284 introduced the ability to have functions, subscripts, and initiator, as long as a variable parameter has all tags. Before Swift 5.4, in this case you could only have one variable parameter.

Therefore, with this improvement, we can write a function that accepts a variable parameter that stores the times when goals are scored during a football match, and a second variable parameter that scores the names of players who score goals:

func summarizeGoals(times: Int..., players: String...) {
let joinedNames = ListFormatter.localizedString(byJoining: players)
let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init))
print("\(times.count) goals where scored by \(joinedNames) at the follow minutes: \(joinedTimes)")
}

To call this function, provide both sets of values as variable parameters and make sure that all subsequent parameters are labeled:

summarizeGoals (times: 18, 33, 55, 90, players: "Dany", "Jamie", "Rayy")

Result builders

Function builders unofficially came to Swift 5.1, but in the period up to Swift 5.4, they officially passed the Swift Evolution proposal process as SE-0289 to be discussed and improved . As part of this process, they were renamed to result builders and even developed some new functions to better reflect their true purpose.

First, the most important part: result builders allow us to create a new value step by step, passing a sequence that we have chosen. They power large parts of SwiftUI's vision-building system, so VStackWhen you have various opinions inside, Swift can TupleViewgrouping in type so that they can be stored as a single data VStack- displays an array in a single view.

Here's a function that returns a single string:

func makeSentence1() -> String {
"Why settle for a Duke when you can have a Prince?"
}
print(makeSentence1())

This works great, but what if we had a few shows we wanted to put together? Just like SwiftUI, we can provide them all separately and ask Swift to solve this:

This is invalid Swift, and will not compile.
func makeSentence2() -> String {
"Why settle for a Duke"
"when you can have"
"a Prince?"
// }

On its own, this code won't work because Swift doesn't understand what we mean anymore. However, as follows, we can create a result generator that understands how we can convert several strings to a single string using the transformation we want:

@resultBuilder
struct SimpleStringBuilder {
staticfunc buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
}

Although this is a small amount of code, there is a lot to open:

  • @resultBuilderThe attribute must be treated as a result generator. This behavior has been used before @_functionBuilderwas performed using an underscore that indicates that this is not intended for general use.
  • Each result builder buildBlock()must provide at least one static method that must receive and convert some type of data. The example above retrieves, combines, and returns zero or more strings as a single string.
  • The result is that our structure SimpleStringBuilderbecome a result buildes, that is, @SimpleStringBuilderit means that we can use string joining powers wherever we need them.

This SimpleStringBuilder.buildBlock()there is nothing to prevent us from using it directly:

let joined = SimpleStringBuilder.buildBlock(
"Why settle for a Duke",
"when you can have",
"a Prince?"
)
print(joined)

nevertheless @resultBuilderannotation SimpleStringBuilderSince we use it with our structure, we can also apply it to functions such as:

@SimpleStringBuilder func makeSentence3() -> String {
"Why settle for a Duke"
"when you can have"
"a Prince?"
}
print(makeSentence3())

Make sure we no longer need commas at the end of each string — @resultBuildereach expression automatically makeSentence()converts to a single string SimpleStringBuilder.

In practice, result builders can do significantly more by adding more methods to your type. for example SimpleStringBuilderwe can add if/else support by adding two extra methods that explain how we want to convert data. We do not want to convert our strings in our code at all, so we can send them back immediately:

@resultBuilder
struct ConditionalStringBuilder {
staticfunc buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
staticfunc buildEither(first component: String) -> String {
return component
}
staticfunc buildEither(second component: String) -> String {
return component
}
}

Now our functions can use the conditions:

@ConditionalStringBuilder func makeSentence4() -> String {
"Why settle for a Duke"
"when you can have"
if Bool.random() {
"a Prince?"
} else {
"a King?"
}
}
print(makeSentence4())

Similarly, buildArray()We can add support for loops by adding a method to our constructor type:

@resultBuilder
struct ComplexStringBuilder {
staticfunc buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
staticfunc buildEither(first component: String) -> String {
return component
}
staticfunc buildEither(second component: String) -> String {
return component
}
staticfunc buildArray(_ components: [String]) -> String {
components.joined(separator: "\n")
}
}

And now forcycles we can use :

@ComplexStringBuilder func countDown() -> String {
for i in (0...10).reversed() {
"\(i)..."
}
"Lift off!"
}
print(countDown())

Result builder does almost all the work for us.

It's worth adding that Swift 5.4 extends the result builder system to support attributes placed in stored properties , which automatically sets the impulse member initiator for structures to apply to the result builder.

This is especially useful for custom SwiftUI view that uses result builders, such as:

struct CustomVStack: View {
@ViewBuilder let content: Content
var body: some View {
VStack {
// custom functionality here
content
}
}
}

Local functions overloading

SR-10069 , local function has come to the ability to overload so Swift chooses which one to run based on the types used.

For example, if we want to make some simple cookies, we can write code as follows:

struct Butter { }
struct Flour { }
struct Sugar { }
func makeCookies() {
func add(item: Butter) {
print("Adding butter…")
}
func add(item: Flour) {
print("Adding flour…")
}
func add(item: Sugar) {
print("Adding sugar…")
}
add(item: Butter())
add(item: Flour())
add(item: Sugar())
}

Before Swift 5.4, three add()methor could only be overloaded if they were not nested makeCookies(), but function overload from Swift 5.4 is also supported in this case.

Property wrappers

Property wrappers were first introduced in Swift 5.1 as a way to add extra functionality to features in an easy and reusable way, but in Swift 5.4 their behavior was expanded to support its use as native variables in functions.

For example, we can create a property wrappers that ensure that its value never falls below zero:

@propertyWrapper struct NonNegative {
var value: T
var wrappedValue: T {
get { value }
set {
if newValue < 0 {
value = 0
} else {
value = newValue
}
}
}
init(wrappedValue: T) {
if wrappedValue < 0 {
self.value = 0
} else {
self.value = wrappedValue
}
}
}

And starting with Swift 5.4, we can use these property wrappers in a normal function instead of just attaching them to a property. For example, we can write a game where our player can earn or lose points, but their score should never fall below 0:

func playGame() {
@NonNegative score = 0
player was correct
score += 4
player was correct again
score += 8
player got one wrong
score -= 15
player got another wrong
score -= 16
print(score)
}

Note: Swift 5.4, 1 is only available through Xcode 12.5.

  Quote

KARABAY A, 2021 . New Features with Swift 5.4,

https://www.karabayyazilim.com/blog/diger/swift-54-ile-yeni-gelen-ozellikler-2021-02-24-145655

(Accessed February 24, 2021).


  Share this post

Comments (0)

Comment

Subscribe
Sign up for the email newsletter to be the first to know about my blog posts