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ă.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
What’s New in Swift 4.2?
30 mins
- Getting Started
- Language Improvements
- Generating Random Numbers
- Dynamic Member Lookup
- Enumeration Cases Collections
- New Sequence Methods
- Testing Sequence Elements
- Conditional Conformance Updates
- Hashable Improvements
- Removing Elements From Collections
- Toggling Boolean States
- New Compiler Directives
- New Pointer Functions
- Memory Layout Updates
- Inline Functions in Modules
- Miscellaneous Bits and Pieces
- Swift Package Manager Updates
- Removing Implicitly Unwrapped Optionals
- Where to Go From Here?
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:
- You conform
SeasonstoCaseIterableto create the array of enumeration cases. - You loop through
allCasesand 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!
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:
- You create a reversed version of
ageswithreversed(). - You use
first(where:)to determine the last teenager’s age inreversedAges,index(where:)for the last teenager’s index andindex(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
- You make
Tutorialconform toEquatable. - You make
Screencastgeneric, since website authors base their screencasts on published tutorials. - You implement
==(lhs:rhs:)for screencasts sinceScreencastconforms toEquatableas long asTutorialdoes. - 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:
- You conform
Tutorialto bothHashableandCodable. - You constrain
Screencastto conform toHashableandCodableifTutorialdoes. - 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:
- You define
Instrumentwith a certain brand. - You declare
Tuneablefor all instruments that can tune. - You override
tune()inKeyboardto return keyboard standard tuning. - You use
whereto constrainArrayto conform toTuneableas long asElementdoes. - You add an
Instrumentand aKeyboardtoinstruments. - You check if
instrumentsimplementsTuneableand tune it if the test succeeds. In this example, the array can't be cast toTuneablebecause theInstrumenttype 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.
