What’s New in Swift 5.1? [FREE]
source link: https://www.tuicool.com/articles/Z7ZJFzJ
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.
Good news: Swift 5.1 is now available in Xcode 11 beta! This release brings module stability and improves the language with important features. In this tutorial, you’ll learn about what’s new in Swift 5.1. You’ll need Xcode 11 beta to work with Swift 5.1, so go ahead and install it before getting started.
Getting Started
Swift 5.1 is source compatible with Swift 5. It’s also binary compatible with Swift 5 and future releases of Swift thanks to ABI stability .
Swift 5.1 adds module stability on top of ABI stability introduced in Swift 5. While ABI stability takes care of app compatibility at runtime, module stability enables library compatibility at compile time. This means you can use a third-party framework with any compiler version instead of only the one it was built with.
Each tutorial section contains Swift Evolution proposal numbers such as [SE-0001] . You can explore each change by clicking the linked tag of each proposal.
I recommend you follow the tutorial by trying out the new features in a playground. Start Xcode 11 and go to File ▸ New ▸ Playground . Choose iOS for the platform and Blank as its template. Name it and save it where you want. Time to get started!
Note : Need a refresher of the Swift 5 highlights? Check out the Swift 5 tutorial: What’s New in Swift 5?
Language Improvements
There are a number of language improvements in this release, including opaque result types, function builders, property wrappers and more.
Opaque Result Types
You may use protocols as return types for functions in Swift 5.
With your new Playground open, open the Project Navigator
by navigating to View ▸ Navigators ▸ Show Project Navigator
. Right-click on the Sources
folder, select New File
and name the file BlogPost
. Replace the contents of the new file with the definition of a new protocol named BlogPost
.
public protocol BlogPost { var title: String { get } var author: String { get } }
Right-click on the top level playground and select New Playground Page . Rename the new playground page Opaque Tutorials and paste this in it:
// 1 struct Tutorial: BlogPost { let title: String let author: String } // 2 func createBlogPost(title: String, author: String) -> BlogPost { guard !title.isEmpty && !author.isEmpty else { fatalError("No title and/or author assigned!") } return Tutorial(title: title, author: author) } // 3 let swift4Tutorial = createBlogPost(title: "What's new in Swift 4.2?", author: "Cosmin Pupăză") let swift5Tutorial = createBlogPost(title: "What's new in Swift 5?", author: "Cosmin Pupăză")
Going over this step by step:
-
Declare
title
andauthor
forTutorial
sinceTutorial
implementsBlogPost
. -
Check if
title
andauthor
are valid and returnTutorial
fromcreateBlogPost(title:author:)
if the test succeeds. -
Use
createBlogPost(title:author:)
to createswift4Tutorial
andswift5Tutorial
.
You can reuse the prototype and logic of createBlogPost(title:author:)
to create screencasts as well because screencasts are blog posts under the hood too.
Right-click on the top level playground and select New Playground Page . Rename the new playground page Opaque Screencasts and paste this in it:
struct Screencast: BlogPost { let title: String let author: String } func createBlogPost(title: String, author: String) -> BlogPost { guard !title.isEmpty && !author.isEmpty else { fatalError("No title and/or author assigned!") } return Screencast(title: title, author: author) } let swift4Screencast = createBlogPost(title: "What's new in Swift 4.2?", author: "Josh Steele") let swift5Screencast = createBlogPost(title: "What's new in Swift 5?", author: "Josh Steele")
Screencast
implements BlogPost
, so you return Screencast
from createBlogPost(title:author:)
and use createBlogPost(title:author:)
to create swift4Screencast
and swift5Screencast
this time.
Navigate to BlogPost.swift
in the Sources
folder and make BlogPost
conform to Equatable
.
public protocol BlogPost: Equatable { var title: String { get } var author: String { get } }
At this point, you’ll get an error that BlogPost
can only be used as a generic constraint. This is because Equatable
has an associated type called Self
. Protocols with associated types are not types, even though they look like types. Instead, they’re kind of like type placeholders saying “this can be any concrete type that conforms to this protocol”.
Swift 5.1 lets you use these protocols as regular types with opaque result types [ SE-0244 ].
In the Opaque Tutorials
page, add some
to the return type of createBlogPost
, saying that it returns a concrete implementation of BlogPost
.
func createBlogPost(title: String, author: String) -> some BlogPost {
Similarly, in the Opaque Screencasts
page, use some
to tell the compiler createBlogPost
returns some type of BlogPost
.
func createBlogPost(title: String, author: String) -> some BlogPost {
You may return any concrete type that implements BlogPost
from createBlogPost
: Tutorial
or Screencast
in this case.
Now, you can check if the previously created tutorials and screencasts are the same. At the bottom of Opaque Tutorials
, paste the following to check if swift4Tutorial
and swift5Tutorial
are the same.
let sameTutorial = swift4Tutorial == swift5Tutorial
At the bottom of Opaque Screencasts
, paste the following to check if swift4Screencast
and swift5Screencast
are the same.
let sameScreencast = swift4Screencast == swift5Screencast
Implicit Returns From Single-Expression Functions
You use return
in single-expression functions in Swift 5:
extension Sequence where Element == Int { func addEvenNumbers() -> Int { return reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 } } func addOddNumbers() -> Int { return reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 } } } let numbers = [10, 5, 2, 7, 4] let evenSum = numbers.addEvenNumbers() let oddSum = numbers.addOddNumbers()
You use reduce(_:_:)
in addEvenNumbers()
and addOddNumbers()
to determine the sum of even and odd numbers in Sequence
.
Swift 5.1 drops return
in single-expression functions so they behave like single-line closures in this case [ SE-0255
]:
extension Sequence where Element == Int { func addEvenNumbers() -> Int { reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 } } func addOddNumbers() -> Int { reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 } } }
The code is cleaner and easier to follow this time.
Note
: Want to learn more about how reduce(_:_:)
works in Swift? Check out the functional programming tutorial: An Introduction to Functional Programming in Swift.
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.
Note
: In the Xcode beta, the annotation for function builders is @_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.
Working with computed properties the Swifty way!
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
.
Diffing collections like a pro in Swift 5.1!
Static and Class Subscripts
Swift 5.1 lets you declare static and class subscripts in classes [ SE-0254 ]:
// 1 @dynamicMemberLookup class File { let name: String init(name: String) { self.name = name } // 2 static subscript(key: String) -> String { switch key { case "path": return "custom path" default: return "default path" } } // 3 class subscript(dynamicMember key: String) -> String { switch key { case "path": return "custom path" default: return "default path" } } } // 4 File["path"] File["PATH"] File.path File.PATH
This is how it all works:
-
Mark
File
as@dynamicMemberLookup
to enable dot syntax for custom subscripts. -
Create a static subscript that returns the default or custom path for
File
. - Define the class version of the previous subscript using dynamic member lookup.
- Call both subscripts with the corresponding syntax.
Note : Want to learn more about subscripts in Swift? Check out the subscripts tutorial: Custom Subscripts in Swift.
Dynamic Member Lookup for Keypaths
Swift 5.1 implements dynamic member lookup for keypaths [ SE-0252 ]:
// 1 struct Point { let x, y: Int } // 2 @dynamicMemberLookup struct Circle<T> { let center: T let radius: Int // 3 subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U { center[keyPath: keyPath] } } // 4 let center = Point(x: 1, y: 2) let circle = Circle(center: center, radius: 1) circle.x circle.y
Going over all of this step-by-step:
-
Declare
x
andy
forPoint
. -
Annotate
Circle
with@dynamicMemberLookup
to enable dot syntax for its subscripts. -
Create a generic subscript which uses keypaths to access
center
properties fromCircle
. -
Call
center
properties oncircle
using dynamic member lookup instead of keypaths.
Note : Need more details about how dynamic member lookup works in Swift? Check out the dynamic features tutorial: Dynamic Features in Swift.
Keypaths for Tuples
You can use keypaths for tuples in Swift 5.1:
// 1 struct Instrument { let brand: String let year: Int let details: (type: String, pitch: String) } // 2 let instrument = Instrument(brand: "Roland", year: 2019, details: (type: "acoustic", pitch: "C")) let type = instrument[keyPath: \Instrument.details.type] let pitch = instrument[keyPath: \Instrument.details.pitch]
Here what's going on:
-
Declare
brand
,year
anddetails
forInstrument
. -
Use keypaths to get
type
andpitch
fromdetails
ininstrument
.
Equatable and Hashable Conformance for Weak and Unowned Properties
Swift 5.1 automatically synthesizes Equatable
and Hashable
conformance for structures with weak
and unowned
stored properties.
Suppose you have two classes:
class Key { let note: String init(note: String) { self.note = note } } extension Key: Hashable { static func == (lhs: Key, rhs: Key) -> Bool { lhs.note == rhs.note } func hash(into hasher: inout Hasher) { hasher.combine(note) } } class Chord { let note: String init(note: String) { self.note = note } } extension Chord: Hashable { static func == (lhs: Chord, rhs: Chord) -> Bool { lhs.note == rhs.note } func hash(into hasher: inout Hasher) { hasher.combine(note) } }
Key
and Chord
both conform to Equatable
and Hashable
by implementing ==(lhs:rhs:)
and hash(into:)
.
If you use those classes in a struct, Swift 5.1 will be able to synthesize Hashable
conformance:
struct Tune: Hashable { unowned let key: Key weak var chord: Chord? } let key = Key(note: "C") let chord = Chord(note: "C") let tune = Tune(key: key, chord: chord) let chordlessTune = Tune(key: key, chord: nil) let sameTune = tune == chordlessTune let tuneSet: Set = [tune, chordlessTune] let tuneDictionary = [tune: [tune.key.note, tune.chord?.note], chordlessTune: [chordlessTune.key.note, chordlessTune.chord?.note]]
Tune
is Equatable
and Hashable
because key
and chord
are Equatable
and Hashable
.
Because it's Hashable
, you can compare tune
with chordlessTune
, add them to tuneSet
and use them as keys for tuneDictionary
.
Ambiguous Enumeration Cases
Swift 5.1 generates warnings for ambiguous enumeration cases:
// 1 enum TutorialStyle { case cookbook, stepByStep, none } // 2 let style: TutorialStyle? = .none
Here’s how this works:
-
Define different styles for
TutorialStyle
. -
Swift fires a warning since it’s not clear to the compiler what
.none
means in this case:Optional.none
orTutorialStyle.none
.
Matching Optional Enumerations Against Non-optionals
You use the optional pattern to match non-optionals with optional enumerations in Swift 5:
// 1 enum TutorialStatus { case written, edited, published } // 2 let status: TutorialStatus? = .published switch status { case .written?: print("Ready for editing!") case .edited?: print("Ready to publish!") case .published?: print("Live!") case .none: break }
The above code does the following:
TutorialStatus status
Swift 5.1 removes optional pattern matching in this case:
switch status { case .written: print("Ready for editing!") case .edited: print("Ready to publish!") case .published: print("Live!") case .none: break }
The code is cleaner and easier to understand this time.
Note : Want to learn more about pattern matching in Swift? Check out the pattern matching tutorial: Pattern Matching in Swift.
New Features for Strings
Swift 5.1 adds some much-needed features to strings [ SE-0248 ]:
UTF8.width("S") UTF8.isASCII(83)
Here you determine the UTF-8 encoding width of the Unicode scalar value and check if the given code unit represents an ASCII scalar. Have a look at the proposal for other APIs you can use.
Contiguous Strings
Swift 5.1 implements important changes to contiguous strings [ SE-0247 ]:
var string = "Swift 5.1" if !string.isContiguousUTF8 { string.makeContiguousUTF8() }
You check if the UTF-8 encoded string is contiguous with isContiguousUTF8
and use makeContiguousUTF8()
to make it so, if not. Take a look at the proposal to see what else you can do with contiguous strings.
Miscellaneous Bits and Pieces
There are a few other features in Swift 5.1 you should know about:
Converting Tuple Types
Swift 5.1 improves conversion of tuple types:
let temperatures: (Int, Int) = (25, 30) let convertedTemperatures: (Int?, Any) = temperatures
You assign temperatures
to convertedTemperatures
because you can convert (Int, Int)
to (Int?, Any)
in this case.
Tuples with Duplicate Labels
You can declare tuples with duplicate labels in Swift 5:
let point = (coordinate: 1, coordinate: 2) point.coordinate
It’s not clear if coordinate
returns the first or second element from point
in this case, so Swift 5.1 removes duplicate labels for tuples.
Overloading Functions With Any Parameters
Swift 5 prefers Any
parameters instead of generic arguments for overloads of functions with only one parameter:
func showInfo(_: Any) -> String { return "Any value" } func showInfo<T>(_: T) -> String { return "Generic value" } showInfo("Swift 5")
showInfo()
returns "Any value"
in this case. Swift 5.1 works the other way round:
func showInfo(_: Any) -> String { "Any value" } func showInfo<T>(_: T) -> String { "Generic value" } showInfo("Swift 5.1")
showInfo()
returns "Generic value"
this time.
Type Aliases for Autoclosure Parameters
You can’t declare type aliases for @autoclosure
parameters in Swift 5:
struct Closure<T> { func apply(closure: @autoclosure () -> T) { closure() } }
apply(closure:)
uses the autoclosure signature for closure
in this case. You may use type aliases in the prototype of apply(closure:)
in Swift 5.1:
struct Closure<T> { typealias ClosureType = () -> T func apply(closure: @autoclosure ClosureType) { closure() } }
apply(closure:)
uses ClosureType
for closure
this time.
Returning Self From Objective-C methods
You have to inherit from NSObject
if your class contains an @objc
method which returns Self
in Swift 5:
class Clone: NSObject { @objc func clone() -> Self { return self } }
Clone
extends NSObject
because clone()
returns Self
. This is no longer the case in Swift 5.1:
class Clone { @objc func clone() -> Self { self } }
Clone
doesn’t have to inherit from anything this time.
Stable ABI Libraries
You use -enable-library-evolution
in Swift 5.1 to make changes to library types without breaking its ABI. Structures and enumerations marked as @frozen
can’t add, remove or reorder stored properties and cases [ SE-0260
].
Where to Go From Here?
You can download the final playground using the Download Materials link at the top or bottom of this tutorial.
Swift 5.1 adds many nice features to the ones already introduced in Swift 5. It also brings module stability to the language and implements complex paradigms used by the new frameworks introduced at WWDC like SwiftUI and Combine.
You can read more about the changes in this Swift version on the official Swift CHANGELOG or the Swift standard library differences .
You can also have a look at the Swift Evolution proposals to see what’s coming in the next version of Swift. Here, you can offer feedback for currently reviewed proposals and even pitch a proposal yourself!
What is your favorite Swift 5.1 feature so far? Let us know in the forum discussion below!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK