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
Seasons
toCaseIterable
to create the array of enumeration cases. - 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!
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
ages
withreversed()
. - 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
Tutorial
conform toEquatable
. - You make
Screencast
generic, since website authors base their screencasts on published tutorials. - You implement
==(lhs:rhs:)
for screencasts sinceScreencast
conforms toEquatable
as long asTutorial
does. - 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
Tutorial
to bothHashable
andCodable
. - You constrain
Screencast
to conform toHashable
andCodable
ifTutorial
does. - 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
Instrument
with a certain brand. - You declare
Tuneable
for all instruments that can tune. - You override
tune()
inKeyboard
to return keyboard standard tuning. - You use
where
to constrainArray
to conform toTuneable
as long asElement
does. - You add an
Instrument
and aKeyboard
toinstruments
. - You check if
instruments
implementsTuneable
and tune it if the test succeeds. In this example, the array can't be cast toTuneable
because theInstrument
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
.