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

Adding Result to the Standard Library

Swift 5 adds Result to the standard library [SE-0235]:

// 1
enum ConnectionError: Error {
  case noNetwork, noDatabase
}

// 2
let networkSuccess = Result<String, ConnectionError>.success("Network connected!")
let databaseSuccess = Result<String, ConnectionError>.success("Database connected!")
let networkFailure = Result<String, ConnectionError>.failure(.noNetwork)
let databaseFailure = Result<String, ConnectionError>.failure(.noDatabase)
let sameSuccess = networkSuccess == databaseSuccess
let sameFailure = networkFailure == databaseFailure
let success: Set = [networkSuccess, databaseSuccess]
let failure: Set = [networkFailure, databaseFailure]
let successDictionary = [
  networkSuccess: try! networkSuccess.get(),
  databaseSuccess: try! databaseSuccess.get()
]
let failureDictionary = [
  networkFailure: ConnectionError.noNetwork,
  databaseFailure: ConnectionError.noDatabase
]

Here’s how this code works:

  1. Declare the most common connection errors.
  2. Compare connection results, add them to sets. You use these sets as keys for dictionaries, since Result implements Equatable and Hashable.

Conforming Never to Equatable and Hashable

Swift 5 conforms Never to Equatable and Hashable [SE-0215]:

let alwaysSucceeds = Result<String, Never>.success("Network connected!")
let neverFails = Result<String, Never>.success("Database connected!")
let alwaysFails = Result<Never, ConnectionError>.failure(.noNetwork)
let neverSucceeds = Result<Never, ConnectionError>.failure(.noDatabase)
let sameValue = alwaysSucceeds == neverFails
let sameError = alwaysFails == neverSucceeds
let alwaysSuccess: Set = [alwaysSucceeds, neverFails]
let alwaysFailure: Set = [alwaysFails, neverSucceeds]
let alwaysSuccessDictionary = [
  alwaysSucceeds: try! alwaysSucceeds.get(),
  neverFails: try! neverFails.get()
]
let alwaysFailureDictionary = [
  alwaysFails: ConnectionError.noNetwork,
  neverSucceeds: ConnectionError.noDatabase
]

In this code, you define connection results that always return values or errors, compare them, add them to sets and use them as dictionary keys.

Dynamically Callable Types

Swift 5 defines dynamically callable types that interoperate with scripting languages like Python or Ruby [SE-0216]:

// 1
@dynamicCallable
class DynamicFeatures {
  // 2
  func dynamicallyCall(withArguments params: [Int]) -> Int? {
    guard !params.isEmpty else {
      return nil
    }
    return params.reduce(0, +)
  }
  
  func dynamicallyCall(withKeywordArguments params: KeyValuePairs<String, Int>) -> Int? {
    guard !params.isEmpty else {
      return nil
    }
    return params.reduce(0) { $1.key.isEmpty ? $0 : $0 + $1.value }
  }
}

// 3
let features = DynamicFeatures()
features() // nil
features(3, 4, 5) // 12
features(first: 3, 4, second: 5) // 8

The code above works as follows:

  1. Mark DynamicFeatures as @dynamicCallable to make it a dynamically callable type.
  2. To make DynamicFeatures conform to @dynamicCallable, implement dynamicallyCall(withArguments:) and dynamicallyCall(withKeywordArguments:).
  3. Invoke features using normal syntax, and the compiler calls dynamicallyCall(withArguments:) or dynamicallyCall(withKeywordArguments:).

Swift Package Manager Updates

Swift 5 adds a few features to the Swift Package Manager:

Platform Deployment Settings

Swift 5 allows you to define the minimum required platform deployment target version in Package.swift [SE-0236]:

let package = Package(name: “Package”, platforms: [
  .macOS(.v10_14), 
  .iOS(.v12),
  .tvOS(.v12), 
  .watchOS(.v5)
])

You use macOS(), iOS(), tvOS() and watchOS() in SupportedPlatform to set the minimum required platform version for package.

Target Build Settings

Swift 5 declares target-specific build settings in Package.swift. They customize how the package manager invokes build tools during target builds [SE-0238].

Dependency Mirroring

Swift 5 brings dependency mirroring to the Swift Package Manager [SE-0219].

swift package config set-mirror --package-url <package> --mirror-url <mirror>

Mirrors give you access to dependencies, even if the original source becomes unavailable or gets deleted.

set-mirror updates a dependency with a mirror, which replaces all other ones.

Use unset-mirror to remove mirrors from dependencies:

swift package config unset-mirror --package-url <package>
swift package config unset-mirror —mirror-url <mirror> 
swift package config unset-mirror --all

Miscellaneous Bits and Pieces

Swift 5 adds a few other much-needed features and improvements:

Making Codable Ranges

Swift 5 adds Codable conformance to ranges [SE-0239]:

let temperature = 0...10
let encoder = JSONEncoder()
let data = try! encoder.encode(temperature)
let decoder = JSONDecoder()
let temperatureRange = try! decoder.decode(ClosedRange<Int>.self, from: data)

You encode temperature with JSONEncoder and decode data with JSONDecoder since ranges implement Codable by default in Swift 5.

Flattening Nested Optionals

Swift 4.2 creates nested optionals with try?:

extension Int {
  // 1
  enum DivisionError: Error {
    case divisionByZero
  }
  
  // 2
  func divideBy(_ number: Int) throws -> Int {
    guard number != 0 else {
      throw DivisionError.divisionByZero
    }
    return self / number
  }
}

// 3
let number: Int? = 10
let division = try? number?.divideBy(2)
if let division = division, 
   let final = division {
  print(final)
}

Here’s what this code does:

  1. Extend Int with DivisionError.
  2. divideBy(_:) throws .divisionByZero if number is 0.
  3. Unwrap division twice since it’s an Int??.

Swift 5 handles this differently [SE-0230]:

if let division = division {
  print(division)
}

try? in Swift 5 doesn’t create nested optionals, so you unwrap division once since it’s an Int?.

Removing Customization Points From Collections

You have access to customization points from Collection in Swift 4.2:

extension Array {
  var first: Element? {
    return !isEmpty ? self[count - 1] : nil
  }
  
  var last: Element? {
    return !isEmpty ? self[0] : nil
  }
}

let names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.first // "Nori"
names.last // "Cosmin"

In this code, first returns the last name from names, and last returns the first element of the array.

Both computed properties don’t work as expected, so Swift 5 removes their customization points from collections [SE-0232].

Identity Key Paths

Swift 4.2 uses .self to access values:

class Tutorial {
  let title: String
  let author: String
  init(title: String, author: String) {
    self.title = title
    self.author = author
  }
}

var tutorial = Tutorial(title: "What's New in Swift 5.0?", author: "Cosmin Pupaza")
tutorial.self = Tutorial(title: "What's New in Swift 5?", author: "Cosmin Pupăză")

In this code, you use .self to change the tutorial’s title and author in one go.

Swift 5 adds identity key paths for value access [SE-0227]:

tutorial[keyPath: \.self] = Tutorial(
  title: "What's New in Swift 5?",
  author: "Cosmin Pupăză")

In this code, you use \.self to update tutorial.

Initializing Literals Through Coercion

In Swift 5, literal initializers coerce the literal to its type if the type conforms to the literal protocol [SE-0213]:

let value = UInt64(0xFFFF_FFFF_FFFF_FFFF)

In Swift 4.2, the line of code above produces an overflow error at compile time.

Build Configuration Updates

Swift 4.2 uses >= in compilation conditions:

let favoriteNumber = 10
var evenNumber = true

#if !swift(>=5)
  evenNumber = favoriteNumber % 2 == 0
#else 
  evenNumber = favoriteNumber.isMultiple(of: 2)
#endif

#if !compiler(>=5)
  evenNumber = favoriteNumber % 2 == 0  
#else
  evenNumber = favoriteNumber.isMultiple(of: 2)
#endif

These conditions check if the Swift version is greater than or equal to 5 and compile those bits of code if the condition is met.

Swift 5 adds < for cleaner conditions [SE-0224]:

#if swift(<5)
  evenNumber = favoriteNumber % 2 == 0   
#else
  evenNumber = favoriteNumber.isMultiple(of: 2)  
#endif

#if compiler(<5)
  evenNumber = favoriteNumber % 2 == 0 
#else
  evenNumber = favoriteNumber.isMultiple(of: 2)   
#endif
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.