What’s New in Swift 4.1?

Swift 4.1 is here! What does it mean for you? In this article, you’ll learn about the most significant changes introduced in Swift 4.1 and the impact they will have on your code. By Cosmin Pupăză.

Leave a rating/review
Download materials
Save for later
Share

Xcode 9.3 and Swift 4.1 are finally out of beta! This release contains some long-awaited improvements to the standard library and language itself. If you haven’t been following the Swift Evolution Process closely, keep reading.

In this tutorial, you’ll learn about the most significant changes introduced in Swift 4.1.

This article requires Xcode 9.3, so make sure you have it installed and ready to go before getting started.

Getting Started

Swift 4.1 is source-compatible with Swift 4, so the new features won’t break your code if you’ve already migrated your project to Swift 4 using the Swift Migrator in Xcode.

In the sections below, you’ll see linked tags such as [SE-0001]. These are Swift Evolution proposal numbers. I’ve included the link to each proposal so you can dig into the full details of each particular change. I recommend you try out the features in a playground so you have a better understanding of everything that changes as you work.

To start, fire up Xcode 9.3 and select File ▸ New ▸ Playground. Choose iOS as the platform and Blank as its template. Name and save it as you like. To get the most out of this tutorial, try out each feature in your new playground as you work.

Note: Need to catch up the highlights of Swift 4? No problem! Check out the predecessor to this tutorial, Swift 4: What’s New in Swift 4.

Language Improvements

There are a number of language improvements in this release, including conditional conformance, recursive constraints on associated types in protocols and more.

Conditional Conformance

Conditional conformance enables protocol conformance for generic types where the type arguments satisfy certain conditions [SE-0143]. This is a powerful feature that makes your code more flexible. You can see how it works with a few examples.

Conditional conformance in the standard library

In Swift 4, you could compare arrays, dictionaries and optionals as long as their elements were Equatable. This worked absolutely fine for basic scenarios such as:

// Arrays of Int
let firstArray = [1, 2, 3]
let secondArray = [1, 2, 3]
let sameArray = firstArray == secondArray

// Dictionaries with Int values
let firstDictionary = ["Cosmin": 10, "George": 9]
let secondDictionary = ["Cosmin": 10, "George": 9]
let sameDictionary = firstDictionary == secondDictionary

// Comparing Int?
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional

Using the == operator to test equality in these examples worked since Int is Equatable in Swift 4. However, comparing collections of optionals was a common situation you might have run into with Swift 4 since optionals do not conform to Equatable. Swift 4.1 fixes this issue using conditional conformance, letting optional types with underlying Equatable types to be compared:

// Array of Int?
let firstArray = [1, nil, 2, nil, 3, nil]
let secondArray = [1, nil, 2, nil, 3, nil]
let sameArray = firstArray == secondArray

// Dictionary with Int? values
let firstDictionary = ["Cosmin": 10, "George": nil]
let secondDictionary = ["Cosmin": 10, "George": nil]
let sameDictionary = firstDictionary == secondDictionary

// Comparing Int?? (Optional of Optional)
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional

Int? is Equatable in Swift 4.1, so the == operator works for [Int?], [String: Int?] and Int??.

A similar problem has been solved when comparing arrays of arrays (e.g. [[Int]]). In Swift 4, you could only compare arrays of sets (e.g. [Set<Int>]), since sets conform to Equatable. Swift 4.1 solves this, since arrays (and dictionaries) are Equatable as long as their underlying values are, too.

let firstArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])]
let secondArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])]

// Will work in Swift 4 and Swift 4.1
// since Set<Int> is Equatable
firstArrayOfSets == secondArrayOfSets

let firstArrayOfArrays = [[1, 2, 3], [3, 4, 5]]
let secondArrayOfArrays = [[1, 2, 3], [3, 4, 5]]

// Caused an error in Swift 4, but works in Swift 4.1
// since Arrays are Equatable in Swift 4.1
firstArrayOfArrays == secondArrayOfArrays

Generally, Swift 4.1’s Optional, Array and Dictionary now conform to Equatable and Hashable whenever their underlying values or elements conform to these protocols.

This is how conditional conformance works in the standard library. Next, you will implement it in your own code.

Conditional conformance in code

You’re going to use conditional conformance to create your own band of musical instruments. Add the following block of code at the bottom of the playground to get started:

// 1 
class LeadInstrument: Equatable {
  let brand: String
  
  init(brand: String) {
    self.brand = brand
  }
  
  func tune() -> String {
    return "Standard tuning."
  }
  
  static func ==(lhs: LeadInstrument, rhs: LeadInstrument) -> Bool {
    return lhs.brand == rhs.brand
  }
}

// 2
class Keyboard: LeadInstrument {
  override func tune() -> String {
    return "Keyboard standard tuning."
  }
}

// 3
class Guitar: LeadInstrument {
  override func tune() -> String {
    return "Guitar standard tuning."
  }
}

Here’s what’s this does step-by-step:

  1. LeadInstrument conforms to Equatable. It has a certain brand and a method named tune() that you’ll eventually use to tune the instrument.
  2. You override tune() in Keyboard to return keyboard standard tuning.
  3. You do the same thing for Guitar.

Next, declare the band of instruments:

// 1  
class Band<LeadInstrument> {
  let name: String
  let lead: LeadInstrument
  
  init(name: String, lead: LeadInstrument) {
    self.name = name
    self.lead = lead
  }
}

// 2
extension Band: Equatable where LeadInstrument: Equatable {
  static func ==(lhs: Band<LeadInstrument>, rhs: Band<LeadInstrument>) -> Bool {
    return lhs.name == rhs.name && lhs.lead == rhs.lead
  }
}

Here’s what you’re doing step-by-step:

  1. You create a class called Band with a generic type – LeadInstrument. Each band has an unique name and lead instrument.
  2. You use where to constrain Band to conform to Equatable as long as LeadInstrument does. Your ability to conform the Band‘s generic LeadInstrument to Equatable is exactly where conditional conformance comes into play.

Next, define your favorite bands and compare them:

// 1
let rolandKeyboard = Keyboard(brand: "Roland")
let rolandBand = Band(name: "Keys", lead: rolandKeyboard)
let yamahaKeyboard = Keyboard(brand: "Yamaha")
let yamahaBand = Band(name: "Keys", lead: yamahaKeyboard)
let sameBand = rolandBand == yamahaBand

// 2
let fenderGuitar = Guitar(brand: "Fender")
let fenderBand = Band(name: "Strings", lead: fenderGuitar)
let ibanezGuitar = Guitar(brand: "Ibanez")
let ibanezBand = Band(name: "Strings", lead: ibanezGuitar)
let sameBands = fenderBand == ibanezBand

In this piece of code, you create two Keyboards and Guitars along with their appropriate Bands. You then compare the bands directly, thanks to the conditional conformance you defined earlier.

Conditional conformance in JSON parsing

Arrays, dictionaries, sets and optionals conform to Codable if their elements conform to Codable in Swift 4.1. Add the following code to your playground to try this:

struct Student: Codable, Hashable {
  let firstName: String
  let averageGrade: Int
}

let cosmin = Student(firstName: "Cosmin", averageGrade: 10)
let george = Student(firstName: "George", averageGrade: 9)
let encoder = JSONEncoder()

// Encode an Array of students
let students = [cosmin, george]
do {
  try encoder.encode(students)
} catch {
  print("Failed encoding students array: \(error)")
}

// Encode a Dictionary with student values
let studentsDictionary = ["Cosmin": cosmin, "George": george]
do {
  try encoder.encode(studentsDictionary)
} catch {
  print("Failed encoding students dictionary: \(error)")
}

// Encode a Set of students
let studentsSet: Set = [cosmin, george]
do {
  try encoder.encode(studentsSet)
} catch {
  print("Failed encoding students set: \(error)")
}

// Encode an Optional Student
let optionalStudent: Student? = cosmin
do {
  try encoder.encode(optionalStudent)
} catch {
  print("Failed encoding optional student: \(error)")
}

You use this code to encode [Student], [String: Student], Set<Student> and Student?. This works smoothly in Swift 4.1 since Student is Codable, which makes these collection types conform to it as well.