What’s New in Swift 5.1?
Swift 5.1 is finally out! This article will take you through the advancements and changes the language has to offer in its latest version. By Cosmin Pupăză.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
What’s New in Swift 5.1?
25 mins
- Getting Started
- Language Improvements
- Opaque Result Types
- Implicit Returns From Single-Expression Functions
- Function Builders
- Property Wrappers
- Synthesizing Default Values for Initializers in Structures
- Self for Static Members
- Creating Uninitialized Arrays
- Diffing Ordered Collections
- Static and Class Subscripts
- Dynamic Member Lookup for Keypaths
- Keypaths for Tuples
- Equatable and Hashable Conformance for Weak and Unowned Properties
- Ambiguous Enumeration Cases
- Matching Optional Enumerations Against Non-optionals
- New Features for Strings
- Contiguous Strings
- Miscellaneous Bits and Pieces
- Converting Tuple Types
- Tuples with Duplicate Labels
- Overloading Functions With Any Parameters
- Type Aliases for Autoclosure Parameters
- Returning Self From Objective-C methods
- Stable ABI Libraries
- Where to Go From Here?
Function Builders
Swift 5.1 uses function builders to implement the builder pattern [SE-XXXX]:
@_functionBuilder
struct SumBuilder {
static func buildBlock(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)
}
}
Annotate SumBuilder
with @_functionBuilder
to make it a function builder type. Function builders are special types of functions where each expression (literals, variable names, function calls, if
statements etc.) is handled separately and used to produce a single value. For instance, you can write a function where each expression adds the result of that expression to an array, thus making your own kind of array literal.
@_functionBuilder
because this proposal has not yet been approved. Once that approval comes, expect the annotation to become @functionBuilder
.You create function builders by implementing different static functions with specific names and type signatures. buildBlock(_: T...)
is the only required one. There are also functions to handle if
statements, optionals and other structures that could be treated as expressions.
You use a function builder by annotating a function or a closure with the class name:
func getSum(@SumBuilder builder: () -> Int) -> Int {
builder()
}
let gcd = getSum {
8
12
5
}
The closure passed to getSum
evaluates each expression (in this case the three numbers) and passes the list of those expressions’ results to the builder. Function builders, together with implicit returns, are the building blocks of SwiftUI’s clean syntax. They also allow you to create your own domain specific languages.
Property Wrappers
You deal with quite a lot of boilerplate code when working with computed properties in Swift 5:
var settings = ["swift": true, "latestVersion": true]
struct Settings {
var isSwift: Bool {
get {
return settings["swift"] ?? false
}
set {
settings["swift"] = newValue
}
}
var isLatestVersion: Bool {
get {
return settings["latestVersion"] ?? false
}
set {
settings["latestVersion"] = newValue
}
}
}
var newSettings = Settings()
newSettings.isSwift
newSettings.isLatestVersion
newSettings.isSwift = false
newSettings.isLatestVersion = false
isSwift
and isLatestVersion
get and set the value of the given key in settings
. Swift 5.1 removes the repetitive code by defining property wrappers [SE-0258]:
// 1
@propertyWrapper
struct SettingsWrapper {
let key: String
let defaultValue: Bool
// 2
var wrappedValue: Bool {
get {
settings[key] ?? defaultValue
}
set {
settings[key] = newValue
}
}
}
// 3
struct Settings {
@SettingsWrapper(key: "swift", defaultValue: false) var isSwift: Bool
@SettingsWrapper(key: "latestVersion", defaultValue: false)
var isLatestVersion: Bool
}
This is how the above code works:
- Annotate
SettingsWrapper
with@propertyWrapper
to make it a property wrapper type. - Use
wrappedValue
to get and setkey
insettings
. - Mark
isSwift
andisLatestVersion
as@SettingsWrapper
to implement them with the corresponding wrapper.
Synthesizing Default Values for Initializers in Structures
Swift 5 doesn’t set initial values for properties in structures by default, so you define custom initializers for them:
struct Author {
let name: String
var tutorialCount: Int
init(name: String, tutorialCount: Int = 0) {
self.name = name
self.tutorialCount = tutorialCount
}
}
let author = Author(name: "George")
Here you set tutorialCount
to 0 if author
has passed his tryout and joined the tutorial team on the website.
Swift 5.1 lets you set default values for structure properties directly, so no need for custom initializers anymore [SE-0242]:
struct Author {
let name: String
var tutorialCount = 0
}
The code is cleaner and simpler this time.
Self for Static Members
You can’t use Self
to reference static members of a data type in Swift 5, so you have to use the type name instead:
struct Editor {
static func reviewGuidelines() {
print("Review editing guidelines.")
}
func edit() {
Editor.reviewGuidelines()
print("Ready for editing!")
}
}
let editor = Editor()
editor.edit()
The editors on the website review the editing guidelines before editing tutorials since they always change.
You can rewrite the whole thing using Self
in Swift 5.1 [SE-0068]:
struct Editor { static func reviewGuidelines() { print("Review editing guidelines.") } func edit() { Self.reviewGuidelines() print("Ready for editing!") } }
You use Self
to call reviewGuidelines()
this time.
Creating Uninitialized Arrays
You can create uninitialized arrays in Swift 5.1 [SE-0245]:
// 1
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) {
buffer, count in
// 2
for i in 0..<5 {
buffer[i] = Bool.random() ? "on" : "off"
}
// 3
count = 5
}
Going over the above code step by step:
- Use
init(unsafeUninitializedCapacity:initializingWith:)
to createrandomSwitches
with a certain initial capacity. - Loop through
randomSwitches
and set each switch state withrandom()
. - Set the number of initialized elements for
randomSwitches
.
Diffing Ordered Collections
Swift 5.1 enables you to determine the differences between ordered collections [SE-0240].
Let's say you have two arrays:
let operatingSystems = ["Yosemite",
"El Capitan",
"Sierra",
"High Sierra",
"Mojave",
"Catalina"]
var answers = ["Mojave",
"High Sierra",
"Sierra",
"El Capitan",
"Yosemite",
"Mavericks"]
operatingSystems
contains all macOS versions since Swift 1 arranged from oldest to newest. answers
lists them in reverse order while adding and removing some of them.
Diffing collections requires that you check for the latest Swift version with #if swift(>=)
because all diffing methods are marked as @available
for Swift 5.1 only:
#if swift(>=5.1)
let differences = operatingSystems.difference(from: answers)
let sameAnswers = answers.applying(differences) ?? []
// ["Yosemite", "El Capitan", "Sierra", "High Sierra", "Mojave", "Catalina"]
Get differences
between operatingSystems
and answers
with difference(from:)
and use applying(_:)
to apply them to answers
.
Alternatively, you can do this manually:
// 1
for change in differences.inferringMoves() {
switch change {
// 2
case .insert(let offset, let element, let associatedWith):
answers.insert(element, at: offset)
guard let associatedWith = associatedWith else {
print("\(element) inserted at position \(offset + 1).")
break
}
print("""
\(element) moved from position \(associatedWith + 1) to position
\(offset + 1).
""")
// 3
case .remove(let offset, let element, let associatedWith):
answers.remove(at: offset)
guard let associatedWith = associatedWith else {
print("\(element) removed from position \(offset + 1).")
break
}
print("""
\(element) removed from position \(offset + 1) because it should be
at position \(associatedWith + 1).
""")
}
}
#endif
Here’s what this code does:
- Use
inferringMoves()
to determine the moves indifferences
and loop through them. - Add
element
atoffset
toanswers
ifchange
is.insert(offset:element:associatedWith:)
and treat the insertion as a move ifassociatedWith
isn’tnil
. - Delete
element
atoffset
fromanswers
ifchange
is.remove(offset:element:associatedWith:)
and consider the removal a move ifassociatedWith
isn’tnil
.