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ă.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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:

  1. Annotate SettingsWrapper with @propertyWrapper to make it a property wrapper type.
  2. Use wrappedValue to get and set key in settings.
  3. Mark isSwift and isLatestVersion as @SettingsWrapper to implement them with the corresponding wrapper.

Working with computed properties the Swifty way!

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:

  1. Use init(unsafeUninitializedCapacity:initializingWith:) to create randomSwitches with a certain initial capacity.
  2. Loop through randomSwitches and set each switch state with random().
  3. 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:

  1. Use inferringMoves() to determine the moves in differences and loop through them.
  2. Add element at offset to answers if change is .insert(offset:element:associatedWith:) and treat the insertion as a move if associatedWith isn’t nil.
  3. Delete element at offset from answers if change is .remove(offset:element:associatedWith:) and consider the removal a move if associatedWith isn’t nil.

Diffing collections like a pro in Swift 5.1!

Diffing collections like a pro in Swift 5.1!