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?
Hashable Improvements
Take the following example in Swift 4.1 which implements custom hash functions for a class:
class Country: Hashable {
let name: String
let capital: String
init(name: String, capital: String) {
self.name = name
self.capital = capital
}
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
var hashValue: Int {
return name.hashValue ^ capital.hashValue &* 16777619
}
}
let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let countries: Set = [france, germany]
let countryGreetings = [france: "Bonjour", germany: "Guten Tag"]
You can add countries to sets and dictionaries here since they are Hashable
. But the hashValue
implementation is hard to understand and isn’t efficient enough for untrusted source values.
Swift 4.2 fixes this by defining universal hash functions [SE-0206]:
class Country: Hashable {
let name: String
let capital: String
init(name: String, capital: String) {
self.name = name
self.capital = capital
}
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(capital)
}
}
Here, you’ve replaced hashValue
with hash(into:)
in Country
. The function uses combine()
to feed the class properties into hasher
. It’s easy to implement, and it improves performance over all previous versions.
Removing Elements From Collections
You’ll often want to remove all occurrences of a particular element from a Collection
. Here’s a way to do it in Swift 4.1 with filter(_:)
:
var greetings = ["Hello", "Hi", "Goodbye", "Bye"]
greetings = greetings.filter { $0.count <= 3 }
You filter greetings
to return only the short greetings. This doesn’t affect the original array, so you have to make the assignment back to greetings
.
Swift 4.2 adds removeAll(_:)
in [SE-0197]:
greetings.removeAll { $0.count > 3 }
This performs the removal in place. Again, you have simplified code and improved efficiency.
Toggling Boolean States
Toggling Booleans! Who hasn’t done something like this in Swift 4.1:
extension Bool {
mutating func toggle() {
self = !self
}
}
var isOn = true
isOn.toggle()
Swift 4.2 adds toggle()
to Bool
under [SE-0199].
New Compiler Directives
Swift 4.2 defines compiler directives that signal issues in your code [SE-0196]:
// 1
#warning("There are shorter implementations out there.")
let numbers = [1, 2, 3, 4, 5]
var sum = 0
for number in numbers {
sum += number
}
print(sum)
// 2
#error("Please fill in your credentials.")
let username = ""
let password = ""
switch (username.filter { $0 != " " }, password.filter { $0 != " " }) {
case ("", ""):
print("Invalid username and password.")
case ("", _):
print("Invalid username.")
case (_, ""):
print("Invalid password.")
case (_, _):
print("Logged in succesfully.")
}
Here’s how this works:
- You use
#warning
as a reminder that the functional approach for adding elements innumbers
is shorter than the imperative one. - You use
#error
to force other developers to enter theirusername
andpassword
before logging in.
New Pointer Functions
withUnsafeBytes(of:_:)
and withUnsafePointer(to:_:)
only worked for mutable variables in Swift 4.1:
let value = 10
var copy = value
withUnsafeBytes(of: ©) { pointer in print(pointer.count) }
withUnsafePointer(to: ©) { pointer in print(pointer.hashValue) }
You had to create a copy
of value
to make both functions work. Swift 4.2 overloads these functions for constants, so you no longer need to save their values [SE-0205]:
withUnsafeBytes(of: value) { pointer in print(pointer.count) }
withUnsafePointer(to: value) { pointer in print(pointer.hashValue) }
Memory Layout Updates
Swift 4.2 uses key paths to query the memory layout of stored properties [SE-0210]. Here's how it works:
// 1
struct Point {
var x, y: Double
}
// 2
struct Circle {
var center: Point
var radius: Double
var circumference: Double {
return 2 * .pi * radius
}
var area: Double {
return .pi * radius * radius
}
}
// 3
if let xOffset = MemoryLayout.offset(of: \Circle.center.x),
let yOffset = MemoryLayout.offset(of: \Circle.center.y),
let radiusOffset = MemoryLayout.offset(of: \Circle.radius) {
print("\(xOffset) \(yOffset) \(radiusOffset)")
} else {
print("Nil offset values.")
}
// 4
if let circumferenceOffset = MemoryLayout.offset(of: \Circle.circumference),
let areaOffset = MemoryLayout.offset(of: \Circle.area) {
print("\(circumferenceOffset) \(areaOffset)")
} else {
print("Nil offset values.")
}
Going over this step-by-step:
- You define the point’s horizontal and vertical coordinates.
- You declare the circle’s
center
,circumference
,area
andradius
. - You use key paths to get the offsets of the circle’s stored properties.
- You return
nil
for the offsets of the circle’s computed properties since they aren’t stored inline.
Inline Functions in Modules
In Swift 4.1, you couldn’t declare inline functions in your own modules. Go to View ▸ Navigators ▸ Show Project Navigator, right-click Sources and select New File. Rename the file FactorialKit.swift and replace its contents with the following block of code:
public class CustomFactorial {
private let customDecrement: Bool
public init(_ customDecrement: Bool = false) {
self.customDecrement = customDecrement
}
private var randomDecrement: Int {
return arc4random_uniform(2) == 0 ? 2 : 3
}
public func factorial(_ n: Int) -> Int {
guard n > 1 else {
return 1
}
let decrement = customDecrement ? randomDecrement : 1
return n * factorial(n - decrement)
}
}
You’ve created a custom version of the factorial implementation. Switch back to the playground and add this code at the bottom:
let standard = CustomFactorial()
standard.factorial(5)
let custom = CustomFactorial(true)
custom.factorial(5)
Here, you’re generating both the default factorial and a random one. Cross-module functions are more efficient when inlined in Swift 4.2 [SE-0193], so go back to FactorialKit.swift and replace CustomFactorial
with the following:
public class CustomFactorial {
@usableFromInline let customDecrement: Bool
public init(_ customDecrement: Bool = false) {
self.customDecrement = customDecrement
}
@usableFromInline var randomDecrement: Int {
return Bool.random() ? 2 : 3
}
@inlinable public func factorial(_ n: Int) -> Int {
guard n > 1 else {
return 1
}
let decrement = customDecrement ? randomDecrement : 1
return n * factorial(n - decrement)
}
}
Here’s what this does:
- You set both
customDecrement
andrandomDecrement
asinternal
and mark them as@usableFromInline
since you use them in the inlined factorial implementation. - You annotate
factorial(_:)
with@inlinable
to make it inline. This is possible because you declared the function aspublic
.