What’s New in Swift 4.2?

Swift 4.2 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.

Enumeration Cases Collections

Swift 4.1 didn’t provide access to collections of enumeration cases by default. This left you with rather inelegant solutions like the following:

enum Seasons: String {
  case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
}

enum SeasonType {
  case equinox
  case solstice
}

let seasons = [Seasons.spring, .summer, .autumn, .winter]
for (index, season) in seasons.enumerated() {
  let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
  print("\(season.rawValue) \(seasonType).")
}

Here, you add Seasons cases to seasons and loop through the array to get each season name and type. But Swift 4.2 can do you one better!

Swift 4.2 adds enumeration cases arrays to enumerations [SE-0194]:

// 1
enum Seasons: String, CaseIterable {
  case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
}

enum SeasonType {
  case equinox
  case solstice
}

// 2
for (index, season) in Seasons.allCases.enumerated() {
  let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
  print("\(season.rawValue) \(seasonType).")
}

Here’s how you can accomplish the same thing in Swift 4.2:

  1. You conform Seasons to CaseIterable to create the array of enumeration cases.
  2. You loop through allCases and print each season name and type.

You have the option of only adding certain cases to the enumeration cases array:

enum Months: CaseIterable {
  case january, february, march, april, may, june, july, august, september, october, november, december          
  
  static var allCases: [Months] {
    return [.june, .july, .august]
  }
}

Here you add only the summer months to allCases since they are the sunniest ones of the year!

Summer is all over the place in Swift 4.2 enumerations!

Summer is all over the place in Swift 4.2 enumerations!

Summer is all over the place in Swift 4.2 enumerations!

You should add all available cases manually to the array if the enumeration contains unavailable ones:

enum Days: CaseIterable {
  case monday, tuesday, wednesday, thursday, friday
  
  @available(*, unavailable)
  case saturday, sunday
  
  static var allCases: [Days] {
    return [.monday, .tuesday, .wednesday, .thursday, .friday]
  }
}

You add only weekdays to allCases because you mark both .saturday and .sunday as unavailable on any version of any platform.

You can also add cases with associated values to the enumeration cases array:

enum BlogPost: CaseIterable {
  case article
  case tutorial(updated: Bool)
  
  static var allCases: [BlogPost] {
    return [.article, .tutorial(updated: true), .tutorial(updated: false)]
  }
}

In this example, you add all types of blog posts on the website to allCases: articles, new tutorials and updated ones.

New Sequence Methods

Swift 4.1 defined Sequence methods that determined either the first index of a certain element, or the first element which satisfied a certain condition:

let ages = ["ten", "twelve", "thirteen", "nineteen", "eighteen", "seventeen", "fourteen",  "eighteen", 
            "fifteen", "sixteen", "eleven"]

if let firstTeen = ages.first(where: { $0.hasSuffix("teen") }), 
   let firstIndex = ages.index(where: { $0.hasSuffix("teen") }), 
   let firstMajorIndex = ages.index(of: "eighteen") {
  print("Teenager number \(firstIndex + 1) is \(firstTeen) years old.")
  print("Teenager number \(firstMajorIndex + 1) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

The Swift 4.1 way of doing things is to use first(where:) to find the first teenager’s age in ages, index(where:) for the first teenager’s index and index(of:) for the index of the first teenager who is 18.

Swift 4.2 renames some of these methods for consistency [SE-0204]:

if let firstTeen = ages.first(where: { $0.hasSuffix("teen") }), 
   let firstIndex = ages.firstIndex(where: { $0.hasSuffix("teen") }), 
   let firstMajorIndex = ages.firstIndex(of:  "eighteen") {
  print("Teenager number \(firstIndex + 1) is \(firstTeen) years old.")
  print("Teenager number \(firstMajorIndex + 1) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

index(where:) becomes firstIndex(where:), and index(of:) becomes firstIndex(of:) to remain consistent with first(where:).

Swift 4.1 also didn’t define any Collection methods for finding either the last index of a certain element or the last element which matched a given predicate. Here’s how you’d handle this in 4.1:

// 1
let reversedAges = ages.reversed()

// 2
if let lastTeen = reversedAges.first(where: { $0.hasSuffix("teen") }), 
   let lastIndex = reversedAges.index(where: { $0.hasSuffix("teen") })?.base, 
   let lastMajorIndex = reversedAges.index(of: "eighteen")?.base {
  print("Teenager number \(lastIndex) is \(lastTeen) years old.")
  print("Teenager number \(lastMajorIndex) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

Looking at this in sections:

  1. You create a reversed version of ages with reversed().
  2. You use first(where:) to determine the last teenager’s age in reversedAges, index(where:) for the last teenager’s index and index(of:) for the index of the last teenager who is 18.

Swift 4.2 adds the corresponding Sequence methods which collapses the above down to:

if let lastTeen = ages.last(where: { $0.hasSuffix("teen") }), 
   let lastIndex = ages.lastIndex(where: { $0.hasSuffix("teen") }), 
   let lastMajorIndex = ages.lastIndex(of: "eighteen") {
  print("Teenager number \(lastIndex + 1) is \(lastTeen) years old.")
  print("Teenager number \(lastMajorIndex + 1) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

You can simply use last(where:), lastIndex(where:) and lastIndex(of:) to find the previous element and specific indices in ages.

Testing Sequence Elements

A fairly simple routine absent from Swift 4.1 is a way to check whether all elements in a Sequence satisfied a certain condition. You could always craft your own approach, though, such as here where you have to determine whether all elements are even:

let values = [10, 8, 12, 20]
let allEven = !values.contains { $0 % 2 == 1 }

Kludgey, isn’t it? Swift 4.2 adds this missing method to Sequence [SE-0207]:

let allEven = values.allSatisfy { $0 % 2 == 0 }

Much better! This simplifies your code and improves its readability.

Conditional Conformance Updates

Swift 4.2 adds several conditional conformance improvements to extensions and the standard library [SE-0143].

Conditional conformance in extensions

Swift 4.1 couldn’t synthesize conditional conformance to Equatable in extensions. Take the following Swift 4.1 snippet as an example:

// 1
struct Tutorial : Equatable {
  let title: String
  let author: String
}

// 2
struct Screencast<Tutorial> {
  let author: String
  let tutorial: Tutorial
}

// 3
extension Screencast: Equatable where Tutorial: Equatable {
  static func ==(lhs: Screencast, rhs: Screencast) -> Bool {
    return lhs.author == rhs.author && lhs.tutorial == rhs.tutorial
  }
}

// 4
let swift41Tutorial = Tutorial(title: "What's New in Swift 4.1?", author: "Cosmin Pupăză")
let swift42Tutorial = Tutorial(title: "What's New In Swift 4.2?", author: "Cosmin Pupăză")
let swift41Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift41Tutorial)
let swift42Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift42Tutorial)
let sameScreencast = swift41Screencast == swift42Screencast
  1. You make Tutorial conform to Equatable.
  2. You make Screencast generic, since website authors base their screencasts on published tutorials.
  3. You implement ==(lhs:rhs:) for screencasts since Screencast conforms to Equatable as long as Tutorial does.
  4. You compare screencasts directly because of the conditional conformance you declared.

Swift 4.2 adds a default implementation for Equatable conditional conformance to an extension:

extension Screencast: Equatable where Tutorial: Equatable {}

This feature applies to Hashable and Codable conformances in extensions as well:

// 1
struct Tutorial: Hashable, Codable {
  let title: String
  let author: String
}

struct Screencast<Tutorial> {
  let author: String
  let tutorial: Tutorial
}

// 2
extension Screencast: Hashable where Tutorial: Hashable {}
extension Screencast: Codable where Tutorial: Codable {}

// 3
let screencastsSet: Set = [swift41Screencast, swift42Screencast]
let screencastsDictionary = [swift41Screencast: "Swift 4.1", swift42Screencast: "Swift 4.2"]

let screencasts = [swift41Screencast, swift42Screencast]
let encoder = JSONEncoder()
do {
  try encoder.encode(screencasts)
} catch {
  print("\(error)")
}

In this block:

  1. You conform Tutorial to both Hashable and Codable.
  2. You constrain Screencast to conform to Hashable and Codable if Tutorial does.
  3. You add screencasts to sets and dictionaries and encode them.

Conditional conformance runtime queries

Swift 4.2 implements dynamic queries of conditional conformances. You can see this in action in the following code:

// 1
class Instrument {
  let brand: String
  
  init(brand: String = "") {
    self.brand = brand
  }
}

// 2
protocol Tuneable {
  func tune()
}

// 3
class Keyboard: Instrument, Tuneable {
  func tune() {
    print("\(brand) keyboard tuning.")
  }
}

// 4
extension Array: Tuneable where Element: Tuneable {
  func tune() {
    forEach { $0.tune() }
  }
}

// 5
let instrument = Instrument()
let keyboard = Keyboard(brand: "Roland")
let instruments = [instrument, keyboard]

// 6
if let keyboards = instruments as? Tuneable {
  keyboards.tune()
} else {
  print("Can't tune instrument.")
}

Here’s what’s going on above:

  1. You define Instrument with a certain brand.
  2. You declare Tuneable for all instruments that can tune.
  3. You override tune() in Keyboard to return keyboard standard tuning.
  4. You use where to constrain Array to conform to Tuneable as long as Element does.
  5. You add an Instrument and a Keyboard to instruments.
  6. You check if instruments implements Tuneable and tune it if the test succeeds. In this example, the array can't be cast to Tuneable because the Instrument type isn't tuneable. If you created an array of two keyboards, the test would pass and the keyboards would be tuned.

Hashable conditional conformance improvements in the standard library

Optionals, arrays, dictionaries and ranges are Hashable in Swift 4.2 when their elements are Hashable as well:

struct Chord: Hashable {
  let name: String
  let description: String?
  let notes: [String]
  let signature: [String: [String]?]
  let frequency: CountableClosedRange<Int>
}

let cMajor = Chord(name: "C", description: "C major", notes: ["C", "E",  "G"], 
                   signature: ["sharp": nil,  "flat": nil], frequency: 432...446)
let aMinor = Chord(name: "Am", description: "A minor", notes: ["A", "C", "E"], 
                   signature: ["sharp": nil, "flat": nil], frequency: 440...446)
let chords: Set = [cMajor, aMinor]
let versions = [cMajor: "major", aMinor: "minor"]

You add cMajor and aMinor to chords and versions. This wasn’t possible prior to 4.2 because String?, [String], [String: [String]?] and CountableClosedRange<Int> weren’t Hashable.

Cosmin Pupăză

Contributors

Cosmin Pupăză

Author

Bhagat Singh

Tech Editor

Chris Belanger

Editor

Vladyslav Mytskaniuk

Illustrator

James Frost

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.