iOS & Swift Tutorials

Learn iOS development in Swift. Over 2,000 high quality tutorials!

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.

2.8/5 40 Ratings

Version

  • Swift 5, iOS 13, Xcode 11

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:

  1. Declare title and author for Tutorial since Tutorial implements BlogPost.
  2. Check if title and author are valid and return Tutorial from createBlogPost(title:author:) if the test succeeds.
  3. Use createBlogPost(title:author:) to create swift4Tutorial and swift5Tutorial.

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:

  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!

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:

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

  1. Declare x and y for Point.
  2. Annotate Circle with @dynamicMemberLookup to enable dot syntax for its subscripts.
  3. Create a generic subscript which uses keypaths to access center properties from Circle.
  4. Call center properties on circle 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:

  1. Declare brand, year and details for Instrument.
  2. Use keypaths to get type and pitch from details in instrument.

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:

  1. Define different styles for TutorialStyle.
  2. Swift fires a warning since it’s not clear to the compiler what .none means in this case: Optional.none or TutorialStyle.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:

  1. Declare all possible states for TutorialStatus.
  2. Use the optional pattern to switch on status since you define it as an optional.

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!

Average Rating

2.8/5

Add a rating for this content

40 ratings

Contributors

Comments