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 3 of 4 of this article. Click here to view the first page.

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.

Hashing sets and dictionaries like a pro in Swift 4.2!

Hashing sets and dictionaries like a pro in Swift 4.2!

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:

  1. You use #warning as a reminder that the functional approach for adding elements in numbers is shorter than the imperative one.
  2. You use #error to force other developers to enter their username and password 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: &copy) { pointer in print(pointer.count) }
withUnsafePointer(to: &copy) { 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:

  1. You define the point’s horizontal and vertical coordinates.
  2. You declare the circle’s center, circumference, area and radius.
  3. You use key paths to get the offsets of the circle’s stored properties.
  4. 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:

  1. You set both customDecrement and randomDecrement as internal and mark them as @usableFromInline since you use them in the inlined factorial implementation.
  2. You annotate factorial(_:) with @inlinable to make it inline. This is possible because you declared the function as public.
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.