Swift 5.1 has finally been released! This article walks you through the improvements and changes that must be provided in the latest version of the language.
Note: the current version is Swift 5, iOS 13, Xcode 11. If you want to reprint this article, please contact the author and give the source address of the article
Good news :Swift 5.1 is now available in Xcode 11 Beta! This version brings module stability and improves the language with important features. In this tutorial, you’ll learn about the new features of Swift 5.1. You’ll need Xcode 11 Beta to be compatible with Swift 5.1, so install it before you start.
An introduction to
Swift 5.1 is compatible with Swift 5. It is also compatible with Swift 5 and future versions of Swift binaries due to ABI stability.
Swift 5.1 adds module stability to the ABI stability introduced in Swift 5. While ABI stability is responsible for application compatibility at run time, module stability makes compile-time library compatibility possible. This means that you can use a third-party framework with any version of the compiler, rather than just the version it builds.
Each tutorial section contains a Swift Evolution suggestion number, such as **[SE-0001]**. You can browse through each change by clicking the link tag for each proposal.
I recommend that you learn this tutorial by trying out new features on the playground. Start Xcode 11 and go to File ▸ New ▸ Playground. Select iOS as the platform and blank as the template. Name it and save it where you want it. It’s time to start!
Note: Need to revisit Swift 5 highlights? Check out Swift 5 Tutorial: What’s new in Swift 5?
Language improvement
There are many language improvements in this release, including opaque result types, function builders, attribute wrappers, and more.
Opaque Result Types
You can use protocol as the return type for functions in Swift 5.
After opening the new Playground, navigate to View ▸ Navigators ▸ Show Project Navigator to open the Project Navigator. Right-click on the Sources folder, select New File and name the File BlogPost. Replace the content of the new file with the definition of a new protocol called BlogPost.
public protocol BlogPost {
var title: String { get }
var author: String { get}}Copy the code
Right-click on the top-level Playground and select New Playground Page. Rename the new Playground page Opaque tutorial and paste it into it:
/ / 1
struct Tutorial: BlogPost {
let title: String
let author: String
}
/ / 2
func createBlogPost(title: String, author: String) -> BlogPost {
guard! title.isEmpty && ! author.isEmptyelse {
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 ă." ")
Copy the code
Step by step:
- Declare the title and author for the tutorial because the tutorial implements BlogPost.
- checktitleandauthorIf the test is successful, the
CreateBlogPost (title: author:)
returnTutorial. - use
createBlogPost(title:author:)
createswift4Tutorialandswift5Tutorial.
You can also reuse the prototype and logic of createBlogPost(Title: Author 🙂 to create screencasts, since screencasts are also blog posts hidden behind the scenes.
Right-click on the top-level Playground and select New Playground Page. Rename the screen capture of the new Playground page, Opaque, and paste it into it:
struct Screencast: BlogPost {
let title: String
let author: String
}
func createBlogPost(title: String, author: String) -> BlogPost {
guard! title.isEmpty && ! author.isEmptyelse {
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")
Copy the code
Screencast implements BlogPost, so you can return Screencast from createBlogPost(title: Author :), And create swift4Screencast and swift5Screencast using createBlogPost(Title: Author :).
Navigate to BlogPost. Swift in the source folder and make BlogPost Equatable.
public protocol BlogPost: Equatable {
var title: String { get }
var author: String { get}}Copy the code
At this point, you get an error that the BlogPost can only be used as a generic constraint. This is because Equatable has an association type named Self. Protocols with associated types are not types, even if they look like types. Instead, they’re kind of like type placeholders that say “this can be any specific type that fits this protocol.”
Swift 5.1 allows you to use these protocols as regular types, using the opaque result type SE-0244.
In Opaque’s tutorial page, add some to the return type of createBlogPost to indicate that it returns a concrete implementation of the BlogPost.
func createBlogPost(title: String, author: String) -> some BlogPost {
Copy the code
Similarly, in Opaque’s screen display page, use some to tell the compiler createBlogPost to return a BlogPost of a certain type.
func createBlogPost(title: String, author: String) -> some BlogPost {
Copy the code
You can return any specific type that implements the BlogPost from createBlogPost: Tutorial or Screencast.
Now you can check that the tutorial and screen capture you created earlier are the same. At the bottom of Opaque Tutorials, paste the following code to check that the swift4Tutorial and swift5Tutorial are the same.
let sameTutorial = swift4Tutorial == swift5Tutorial
Copy the code
At the bottom of the opaque screen shot, paste the following to check whether swift4Screencast and swift5Screencast are the same.
let sameScreencast = swift4Screencast == swift5Screencast
Copy the code
The single-expression function returns implicitly
Use return in Swift 5’s single-expression function:
extension Sequence where Element= =Int {
func addEvenNumbers(a) -> Int {
return reduce(0) {$1.isMultiple(of: 2)? $0 + $1 : $0}}func addOddNumbers(a) -> 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()
Copy the code
Use reduce(_:_:) in addEvenNumbers() and addOddNumbers() to determine the sum of even and odd numbers.
Swift 5.1 reduces the return value of single-expression functions, so that in this case they behave like the single-line closure SE-0255:
extension Sequence where Element= =Int {
func addEvenNumbers(a) -> Int {
reduce(0) {$1.isMultiple(of: 2)? $0 + $1 : $0}}func addOddNumbers(a) -> Int {
reduce(0) {$1.isMultiple(of: 2)? $0 : $0 + $1}}}Copy the code
This time the code is cleaner and easier to understand.
Note: Want to learn more about how reduce(_:_:) works in Swift? See Functional Programming Tutorial: Introduction to Functional Programming in Swift.
Function constructor
Swift 5.1 implements the builder pattern SE-XXXX using the function builder:
@_functionBuilder
struct SumBuilder {
static func buildBlock(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)}}Copy the code
Annotate SumBuilder** with **@_functionBuilder to make it a function generator type. A function constructor is a special type of function in which each expression (literal, variable name, function call, if statement, and so on) is processed individually and used to generate a single value. For example, you can make your own arrays literal by writing a function in which each expression adds the result of that expression to an array.
Note: In Xcode Beta, the comment for the function builder is ** @_FunctionBuilder **, as this recommendation has not been approved yet. Once approved, annotations are expected to become ** @FunctionBuilder **.
Function builders can be created by implementing different static functions with specific names and type signatures. buildBlock(_: T…) Is all that is required. There are also functions that handle if statements, options, and other constructs that can be processed as expressions.
When using a function generator, comment a function or closure with the class name:
func getSum(@SumBuilder builder: (a) -> Int) - >Int {
builder()
}
let gcd = getSum {
8
12
5
}
Copy the code
The closure passed to getSum evaluates each expression (in this case, three numbers) and passes a list of the results of those expressions to the builder. Function builders and implicit returns are the building blocks of SwiftUI’s clean syntax. They also allow you to create your own domain-specific languages.
Attribute packaging
When you work with computed properties in Swift 5, you deal with a lot of boilerplate code:
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
Copy the code
IsSwift and isLatestVersion get and set the value of the given key in Settings. Swift 5.1 removes duplicate code by defining the attribute wrapper 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
}
Copy the code
The above code works as follows:
- Annotate SettingsWrapper** with ** @propertywrapper to make it the propertyWrapper type.
- Use wrappedValue to get and set keys in Settings.
- Tag isSwift and isLatestVersion as ** @settingsWrapper ** to implement them using the appropriate wrappers.
The default value for the initialization function in the composite structure
By default, Swift 5 does not set initial values for properties in structures, so you can 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")
Copy the code
In this case, if the author passes the test and joins the tutorial team on the site, set the tutorialCount to 0.
Swift 5.1 allows default values for structural attributes to be set directly, so the custom initializer SE-0242 is no longer required:
struct Author {
let name: String
var tutorialCount = 0
}
Copy the code
This time the code is cleaner and simpler.
Self of a static member
In Swift 5, you cannot use Self to refer to a static member of a data type, so you must use the type name:
struct Editor {
static func reviewGuidelines(a) {
print("Review editing guidelines.")}func edit(a) {
Editor.reviewGuidelines()
print("Ready for editing!")}}let editor = Editor()
editor.edit()
Copy the code
Editors on the site check the editing guidelines before editing the tutorial, as they are always changing.
You can rewrite the entire code using Self in Swift 5.1 SE-0068:
struct Editor {
static func reviewGuidelines(a) {
print("Review editing guidelines.")}func edit(a) {
Self.reviewGuidelines()
print("Ready for editing!")}}Copy the code
This time use Self to call reviewGuidelines().
Creates an uninitialized array
You can create an uninitialized array 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
}
Copy the code
Step by step through the above code:
- use
init(unsafeUninitializedCapacity:initializingWith:)
Creates a random switch with a specific initial capacity. - Loop through random switches and use
random()
Sets the state of each switch. - Sets the number of initialization elements for random switches.
Diffing command collection
Swift 5.1 allows you to determine the differences between ordered collections of THE SE-0240.
Suppose there are two arrays:
let operatingSystems = ["Yosemite"."El Capitan"."Sierra"."High Sierra"."Mojave"."Catalina"]
var answers = ["Mojave"."High Sierra"."Sierra"."El Capitan"."Yosemite"."Mavericks"]
Copy the code
OperatingSystems includes all versions of macOS, from the oldest to the most recent. The answer lists them in reverse order, adding and removing some at the same time.
The distinction collection requires you to check for the latest Swift release using #if Swift(>=), as all distinction methods are marked **@available for Swift 5.1**:
#if swift(>=5.1)
let differences = operatingSystems.difference(from: answers)
let sameAnswers = answers.applying(differences) ?? []
// ["Yosemite", "El Capitan", "Sierra", "High Sierra", "Mojave", "Catalina"]
Copy the code
Get the difference(from:) between the operating system and the answer and apply them to the answer using apply(_:).
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). """) } } #endifCopy the code
Here’s what this code does:
- use
inferringMoves()
Identify the movements in the differences and loop through them. - ifchangeis
.insert(offset:element:associatedWith:)
, then add elements to the answer at the offset; ifassociatedWithnotnil, the insert is considered a move. - ifchangeis
.remove(offset:element:associatedWith:)
, the element is removed from the offset of the answer ifassociatedWithnotnil, delete is considered a move.
Static and class subscripts
Swift 5.1 allows you to declare static and class subscript SE-0254 in a class:
/ / 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
Copy the code
Here’s what happened:
- Mark the file as **@dynamicMemberLookup** to enable dot syntax for custom subscripts.
- Create a static subscript that returns the default or custom path to the file.
- Use dynamic member lookup for the version of the class that defines the previous subscript.
- These two subscripts are called using the appropriate syntax.
Note: Want to learn more about swift subscript? Check out the subscript tutorial: Customize Swift subscripts.
Dynamically find the path to a member variable
Swift 5.1 implements key path dynamic member lookup 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
Copy the code
Step by step:
- Declare x and y to be points.
- Annotate Circle** with **@dynamicMemberLookup to enable the dot syntax for its subscript.
- Create a generic subscript that accesses the Center property from Circle using the key path.
- Call center properties on circles using dynamic member lookups instead of key paths.
Note: Need more details on how members find work in Swift Dynamic? See dynamic Features tutorial: Dynamic Features in Swift.
Keypaths tuples
You can use the tuple critical path 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]
Copy the code
Here’s what happened:
- Declare the brand, year and details of the instrument.
- Use key paths to get the type and pitch from the details of the instrument.
Equatable and Hashable consistency for weak and unknown attributes
Swift 5.1 automatically synthesizes Equatable and Hashable conformance for structures with weak and unidentified storage features.
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)
}
}
Copy the code
By implementing ==(LHS: RHS 🙂 and hash(into:), Key and Chord are Equatable and Hashable.
If you use these classes in your structure, Swift 5.1 will be able to compose Hashable:
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]]
Copy the code
Tune is Equatable and Hashable, because value and Chord are Equatable and Hashable.
Because it’s Hashable, you can compare Tune to chordlessTune, add them to tuneSet and use them as keys for tuneDictionary.
Optional Enumeration Case
Swift 5.1 generates warnings for optional enumeration cases:
/ / 1
enum TutorialStyle {
case cookbook, stepByStep, none
}
/ / 2
let style: TutorialStyle? =.none
Copy the code
How this works:
- Define different styles for TutorialStyle.
- SwiftA warning is issued because the compiler is not clear
.none
What does it mean in this case:Optional.none
orTutorialStyle.none
.
Matches an optional enumeration that is not optional
You can match non-options with optional enumerations in Swift 5 using optional modes:
/ / 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
}
Copy the code
The above code does the following:
- Declares all possible states of TutorialStatus.
- Open the state with optional mode because you defined it as optional mode.
In this case, Swift 5.1 removes optional pattern matching:
switch status {
case .written:
print("Ready for editing!")
case .edited:
print("Ready to publish!")
case .published:
print("Live!")
case .none:
break
}
Copy the code
This code is clearer and easier to understand.
Note: Want to learn more about pattern matching in Swift? See pattern Matching Tutorial: Pattern Matching in Swift.
New functionality for strings
Swift 5.1 adds some much-needed functionality to strings SE-0248:
UTF8.width("S")
UTF8.isASCII(83)
Copy the code
Here, you determine the UTF-8 encoding width of a Unicode scalar value and check whether a given code unit represents an ASCII scalar. See proposals for other apis you can use.
Continuous string
Swift 5.1 implements important changes to consecutive strings SE-0247:
var string = "Swift 5.1 * * * *"
if! string.isContiguousUTF8 { string.makeContiguousUTF8() }Copy the code
You check whether the UTF-8 encoded string is contiguouswith isContiguousUTF8 and use makeContiguousUTF8() to do so, if not. Take a look at the proposal and see what you can do with sequential strings.
Other improvements
You should be aware of some other features in Swift 5.1:
Converts the tuple type
Swift 5.1 improves tuple type conversions:
let temperatures: (Int.Int) = (25.30)
let convertedTemperatures: (Int? .Any) = temperatures
Copy the code
You can allocate temperatures to convertedTemperatures, because in that case you can convert (Int, Int) to (Int? Any).
A tuple with duplicate labels
You can declare tuples with duplicate labels in Swift 5:
let point = (coordinate: 1, coordinate: 2)
point.coordinate
Copy the code
In this case, it is not clear whether the coordinates return the first or second element from the point, so Swift 5.1 removes duplicate labels for tuples.
Overloaded functions with any arguments
Swift 5 prefers any argument over generic arguments, and function overloading with only one argument:
func showInfo(_: Any) -> String {
return "Any value"
}
func showInfo<T>(_: T) -> String {
return "Generic value"
}
showInfo("Swift 5")
Copy the code
In this case, showInfo() returns “Any value”. Swift 5.1 works in reverse:
func showInfo(_: Any) -> String {
"Any value"
}
func showInfo<T>(_: T) -> String {
"Generic value"
}
showInfo("Swift 5.1 * * * *")
Copy the code
ShowInfo () returns “Generic value” this time.
Type an alias for the autoclose parameter
You cannot declare a type alias for the **@autoclosure** parameter in Swift 5:
struct Closure<T> {
func apply(closure: @autoclosure (a) -> T) {
closure()
}
}
Copy the code
Apply (closure) uses autoclosure to declare the closure in this case. You can use type aliases in the Apply (Closure 🙂 prototype in Swift 5.1:
struct Closure<T> {
typealias ClosureType= () - >T
func apply(closure: @autoclosure ClosureType) {
closure()
}
}
Copy the code
Apply (closure 🙂 uses ClosureType for the closure this time.
Returns self from an Objective-C method
If your class contains a **@objc method that returns Self in Swift 5, you must inherit from NSObject** :
class Clone: NSObject {
@objc func clone(a) -> Self {
return self}}Copy the code
Because Clone extends NSObject, Clone () returns Self. This is no longer the case in Swift 5.1:
class Clone {
@objc func clone(a) -> Self {
self}}Copy the code
Clones don’t have to inherit anything this time.
Stable ABI library
You can use **-enable-library-evolution in Swift 5.1 to change the library type without breaking its ABI**. Structures and enumerations labeled **@frozen** cannot add, delete, or reorder stored properties and cases SE-0260.
And then where?
You can download the final Playground using the Download Materials link at the top or bottom of this tutorial.
Swift 5.1 adds a number of nice features to those already introduced in Swift 5. It also brings modular stability to the language and implements complex paradigms used by new frameworks introduced in WWDC such as SwiftUI and Combine.
You can read more about this Swift version change at the official Swift CHANGELOG or Swift Standard Library Differences.
You can also check out the Swift Evolution proposal to see what’s in the next version of Swift. Here, you can provide feedback on proposals currently being reviewed and even submit your own proposals!
Project example: Project example