What’s New in Swift 5?

Swift 5 is finally available in Xcode 10.2! This release brings ABI stability and improves the language with some long-awaited features. See what’s new! 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.

Removing Subsequences

Swift 4.2 returns SubSequence from Sequence customization points as follows:

extension Sequence {
  func remove(_ s: String) -> SubSequence {
    guard let n = Int(s) else {
      return dropLast()
    }
    return dropLast(n)
  }
}

let sequence = [5, 2, 7, 4]
sequence.remove("2") // [5, 2]
sequence.remove("two") // [5, 2, 7]

In this case, remove(_:) drops the last n elements from the sequence if s is an Int or the last element.

Swift 5 replaces SubSequence with concrete types in sequences [SE-0234]:

extension Sequence {
  func remove(_ s: String) -> [Element] {
    guard let n = Int(s) else {
      return dropLast()
    }
    return dropLast(n)
  }
}

In this code, remove(_:) returns [Element] since dropLast() and dropLast(_:) return [Element].

Dictionary Updates

Swift 5 brings some long-awaited improvements to dictionaries:

Compacting Dictionaries

Swift 4.2 uses mapValues, filter and reduce to filter nil values from dictionaries as follows:

let students = ["Oana": "10", "Nori": "ten"]
let filterStudents = students.mapValues(Int.init)
  .filter { $0.value != nil }
  .mapValues { $0! }
let reduceStudents = students.reduce(into: [:]) { $0[$1.key] = Int($1.value) }

This code uses mapValues with filter or reduce to determine the students with valid grades from students. Both approaches require multiple dictionary passes and complicate your code.

Swift 5 uses compactMapValues(_:) for a more efficient solution [SE-0218]:

let mapStudents = students.compactMapValues(Int.init)

It accomplishes the same thing in much fewer lines of code, neat!

Renaming Dictionary Literals

Swift 4.2 uses DictionaryLiteral to declare dictionaries as follows:

let pets: DictionaryLiteral = ["dog": "Sclip", "cat": "Peti"]

DictionaryLiteral isn’t a dictionary or a literal. It’s a list of key-value pairs.

Swift 5 renames DictionaryLiteral to KeyValuePairs [SE-0214]:

let pets: KeyValuePairs = ["dog": "Sclip", "cat": "Peti"]

Numeric Protocol Updates

Swift 4.2 implements Numeric for vectors:

// 1
struct Vector {
  let x, y: Int
  
  init(_ x: Int, _ y: Int) {
    self.x = x
    self.y = y
  }
}

// 2
extension Vector: ExpressibleByIntegerLiteral {
  init(integerLiteral value: Int) {
    x = value
    y = value
  }
}

// 3
extension Vector: Numeric {
  var magnitude: Int {
    return Int(sqrt(Double(x * x + y * y)))
  }  

  init?<T>(exactly value: T) {
    x = value as! Int
    y = value as! Int
  }
  
  static func +(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
  }
  
  static func +=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs + rhs
  }
  
  static func -(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
  }
  
  static func -=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs - rhs
  }
  
  static func *(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x * rhs.y, lhs.y * rhs.x)
  }
  
  static func *=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs * rhs
  }
}

// 4
extension Vector: CustomStringConvertible {
  var description: String {
    return "(\(x) \(y))"
  }
}

Here’s how this code works:

  1. Declare x, y and init(_:_:) for Vector.
  2. Implement init(integerLiteral:) to conform Vector to ExpressibleByIntegerLiteral as a requirement for Numeric conformance.
  3. Conform Vector to Numeric by defining the vector’s magnitude, declaring init(exactly:) and implementing +(lhs:rhs:), +=(lhs:rhs:), -(lhs:rhs:), -=(lhs:rhs:), *(lhs:rhs:), *=(lhs:rhs:).
  4. Implement description to conform Vector to CustomStringConvertible.

The code above enables you to work with vectors easily:

var first = Vector(1, 2) // (1,2)
let second = Vector(3, 4) // (3,4)
let third = first + second // (4,6)
first += second // (4,6)
let fourth = first - second // (1,2)
first -= second // (1,2)

Swift 5 implements AdditiveArithmetic for vectors, because you can’t define the cross product of 2D vectors [SE-0233]. It doesn’t require ExpressibleByIntegerLiteral conformance:

extension Vector: AdditiveArithmetic {
  static var zero: Vector {
    return Vector(0, 0)
  }
  
  static func +(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
  }
  
  static func +=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs + rhs
  }
  
  static func -(lhs: Vector, rhs: Vector) -> Vector {
    return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
  }
  
  static func -=(lhs: inout Vector, rhs: Vector) {
    lhs = lhs - rhs
  }
}

In this code, you conform Vector to AdditiveArithmetic by defining a zero and implementing +(lhs:rhs:), +=(lhs:rhs:), -(lhs:rhs:), -=(lhs:rhs:).

Working with vectors is so easy in Swift 5!

Working with vectors is so easy in Swift 5!

Working with vectors is so easy in Swift 5!

Note: Want to learn more about operator overloading in Swift? Check out the operator overloading tutorial: Overloading Custom Operators in Swift.

String Interpolation Updates

Swift 4.2 implements string interpolation by interpolating segments:

let language = "Swift"
let languageSegment = String(stringInterpolationSegment: language)
let space = " "
let spaceSegment = String(stringInterpolationSegment: space)
let version = 4.2
let versionSegment = String(stringInterpolationSegment: version)
let string = String(stringInterpolation: languageSegment, spaceSegment, versionSegment)

In this code, the compiler first wraps each literal segment and then interpolates one with init(stringInterpolationSegment:). Then, it wraps all segments together with init(stringInterpolation:).

Swift 5 takes a completely different approach [SE-0228]:

// 1
var interpolation = DefaultStringInterpolation(
  literalCapacity: 7,
  interpolationCount: 1)
// 2
let language = "Swift"
interpolation.appendLiteral(language)
let space = " "
interpolation.appendLiteral(space)
let version = 5
interpolation.appendInterpolation(version)
// 3
let string = String(stringInterpolation: interpolation)

Here’s what this code does:

  1. Define a DefaultStringInterpolation instance with a certain capacity and interpolation count.
  2. Call appendLiteral(_:) or appendInterpolation(_:) to add literals and interpolated values to interpolation.
  3. Produce the final interpolated string by calling init(stringInterpolation:).

Handling Future Enumeration Cases

Swift 4.2 doesn’t handle new enumeration cases properly, as you can see below:

// 1
enum Post {
  case tutorial, article, screencast, course
}

// 2
func readPost(_ post: Post) -> String {
  switch post {
    case .tutorial:
      return "You are reading a tutorial."
    case .article:
      return "You are reading an article."
    default:
      return "You are watching a video."
  }
}

// 3
let screencast = Post.screencast
readPost(screencast) // "You are watching a video."
let course = Post.course
readPost(course) // "You are watching a video."

Here’s what happens in the code above:

  1. Define all types of blog posts on the website.
  2. To make switch exhaustive, add default.
  3. Handle .screencast and .course with default since screencasts and courses are videos.

The following is how handling podcasts works in Swift 4.2:

enum Post {
  case tutorial, article, podcast, screencast, course
}

let podcast = Post.podcast
readPost(podcast) // "You are watching a video."

In this code, you handle .podcast with default, even though podcasts aren’t videos. Swift 4.2 doesn’t warn you about this because the switch is exhaustive.

Swift 5 takes care of added enumeration cases [SE-0192]:

func readPost(_ post: BlogPost) -> String {
  switch post {
    case .tutorial:
      return "You are reading a tutorial."
    case .article:
      return "You are reading an article."
    @unknown default:
      return "You are reading a blog post."
  }
}

readPost(screencast) // "You are reading a blog post."
readPost(course) // "You are reading a blog post."
readPost(podcast) // "You are reading a blog post."

In this code, you mark default as @unknown, and Swift warns you that switch isn’t exhaustive. default handles .screencast, .course and .podcast because screencasts, courses and podcasts are blog posts.

The future is bright for Swift 5 enumerations!

The future is bright for Swift 5 enumerations!

The future is bright for Swift 5 enumerations!

Cosmin Pupăză

Contributors

Cosmin Pupăză

Author

Sarah Reichelt

Tech Editor

Ryan Dube

Editor

Martín Riera

Illustrator

Marin Bencevic

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.