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:

  1. Declare the title and author for the tutorial because the tutorial implements BlogPost.
  2. checktitleandauthorIf the test is successful, theCreateBlogPost (title: author:)returnTutorial.
  3. usecreateBlogPost(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:

  1. Annotate SettingsWrapper** with ** @propertywrapper to make it the propertyWrapper type.
  2. Use wrappedValue to get and set keys in Settings.
  3. 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:

  1. useinit(unsafeUninitializedCapacity:initializingWith:)Creates a random switch with a specific initial capacity.
  2. Loop through random switches and userandom()Sets the state of each switch.
  3. 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:

  1. useinferringMoves()Identify the movements in the differences and loop through them.
  2. ifchangeis.insert(offset:element:associatedWith:), then add elements to the answer at the offset; ifassociatedWithnotnil, the insert is considered a move.
  3. 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:

  1. Mark the file as **@dynamicMemberLookup** to enable dot syntax for custom subscripts.
  2. Create a static subscript that returns the default or custom path to the file.
  3. Use dynamic member lookup for the version of the class that defines the previous subscript.
  4. 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:

  1. Declare x and y to be points.
  2. Annotate Circle** with **@dynamicMemberLookup to enable the dot syntax for its subscript.
  3. Create a generic subscript that accesses the Center property from Circle using the key path.
  4. 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:

  1. Declare the brand, year and details of the instrument.
  2. 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:

  1. Define different styles for TutorialStyle.
  2. SwiftA warning is issued because the compiler is not clear.noneWhat does it mean in this case:Optional.noneorTutorialStyle.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:

  1. Declares all possible states of TutorialStatus.
  2. 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