articles

Swift 5 - Latest Updates 2020

Swift 5 – What’s New

 

Swift 5 is finally available in Xcode 10.2! This release brings ABI stability and improves the language with some long-awaited features.

 

In this article, we’ll discuss the most important changes in Swift 5. Swift 5 requires Xcode 10.2, so make sure to install it before getting started.

 

Getting Started

 

Swift 5 is source compatible with Swift 4.2, but isn’t binary compatible with earlier Swift releases. However, future releases will be binary compatible with Swift 5 thanks to ABI stability.

 

ABI stability enables binary compatibility between apps and libraries compiled with different Swift versions. The Swift standard library and runtime get embedded in the OS, so apps don’t distribute their own copy of the libraries on any platform. This leads to better tool decoupling and OS integration.

 

You also need ABI stability to distribute binary frameworks that work across multiple Swift versions. This requires module format stability, which stabilizes the module file containing the compiler’s representation of the framework’s public interfaces.

 

Language Improvements

 

There are many language features in Swift 5, such as dynamic callable types, handling future enumeration and more.

 

Testing Integer Multiples

 

In Swift 4.2, you determine whether a number is a multiple of another with the remainder operator:


let firstNumber = 4
let secondNumber = 2
if secondNumber != 0 && firstNumber % secondNumber == 0 {
print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}

 

How This Code Works
 

1.    Check that secondNumber isn’t 0.

 

2.    Check that dividing firstNumber by secondNumber returns a remainder of 0.

 

3.    Perform the divide operation.

 

You must check that secondNumber isn’t 0, because % throws an error if it is.

 

Swift 5 simplifies this by adding isMultiple(of:) to BinaryInteger [SE-0225]:


if firstNumber.isMultiple(of: secondNumber) {
print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
}

 

isMultiple(of:) works even if you pass it an argument of 0, and the resulting code is much cleaner.

 

Escaping Raw Strings

 

Swift 4.2 uses escape sequences to represent backslashes and quote marks in strings:

 

let escape = "You use escape sequences for \"quotes\"\\\"backslashes\" in Swift 4.2."

let multiline = """
You use escape sequences for \"\"\"quotes\"\"\"\\\"\"\"backslashes\"\"\"
on multiple lines
in Swift 4.2.
"""

 

Swift 5 adds raw strings. You add # at the beginning and end of the string so you can use backslashes and quote marks without issue. [SE-0200]:


let raw = #"You can create "raw"\"plain" strings in Swift 5."#
let multiline = #"""
You can create """raw"""\"""plain""" strings
on multiple lines
in Swift 5.
"""#

 

When using string interpolation in raw strings, you have to use a pound sign after the backslash:


let track = "Nothing Else Matters"
print(#"My favorite tune\song is \#(track)."#)

 

There are some cases when you need to use more than one # at the beginning and end of the string:


let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."##

 

In the code above, you add ## at the beginning and end of hashtag so that you can represent # inside the string. The number of #s used at the beginning of the string must match the number at the end of it.

 

In Swift 4.2, you escape backslashes inside regular expressions as follows:


// 1
let versions = "3 3.1 4 4.1 4.2 5"
let range = NSRange(versions.startIndex..., in: versions)
// 2
let regex = try! NSRegularExpression(pattern: "\\d\\.\\d")
// 3
let minorVersions = regex.matches(in: versions, range: range)
// 4
minorVersions.forEach { print(versions[Range($0.range, in: versions)!]) }

 

Here’s How This Code Works

 

1.    Declare versions and define a range that covers the whole string.

 

2.    Define a regular expression which matches all minor Swift releases in versions.

 

3.    Determine minor version ranges with matches(in:options:range:).

 

4.    Use the ranges to get the minor releases from versions.

 

Swift 5 simplifies regular expressions with raw strings:


let regex = try! NSRegularExpression(pattern: #"\d\.\d"#)

 

In this code, you write regex using half the number of backslashes, because you don’t need to escape backslashes in raw strings.

 

No extra backslashes in regular expressions!

 

Using New Character Properties

 

Swift 4.2 requires workarounds for common tasks when working with characters:


let id = "ID10"
var digits = 0
id.forEach { digits += Int(String($0)) != nil ? 1 : 0 }
print("Id has \(digits) digits.")

 

In this code, you determine how many digits id has by first casting each character to String, then to Int.

 

However, Swift 5 adds properties to Character which make characters easier to use [SE-0221]:


id.forEach { digits += $0.isNumber ? 1 : 0 }

 

In this case, you use isNumber to check if every character is a digit. Have a look at the proposal for other properties you can use.

 

Using New Unicode Scalar Properties

 

In Swift 4.2, you implement text processing algorithms for unicode scalars as follows:


let username = "bond007"
var letters = 0
username.unicodeScalars.forEach {
letters += (65...90) ~= $0.value || (97...122) ~= $0.value ? 1 : 0
}
print("Username has \(letters) letters.")

 

In this code, you compute how many letters username has by checking if each character’s unicode scalar represents a small letter or a capital one.

 

Swift 5 adds properties to unicode scalars, which simplify text processing [SE-0211]:


username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 }

 

In this code, you use isAlphabetic to check if every character is a digit. The linked proposal shows all the properties you can check.

 

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!

 

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!

 

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 Optional

 

Swift 4.2 creates nested optional 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

 

Using Variadic Parameters For Enumeration Cases With Associated Values

 

You can use variadic parameters for enumeration cases with associated values in Swift 4.2:


enum BlogPost {
case tutorial(_: String...)
case article(_: String...)
}

 

You use String… for tutorials and articles details. This isn't possible anymore in Swift 5, so you should use arrays instead:


enum BlogPost {
case tutorial([String])
case article([String])
}

 

You use [String] to set tutorials and articles details this time.

 

Deprecating String Index Encoded Offsets

 

Swift 4.2 strings are encoded using UTF-16. As a result encodedOffset returns an offset into the UTF-16 string:


let swiftVersion = "Swift 4.2"
let offset = swiftVersion.endIndex.encodedOffset

 

Here you get the offset of endIndex in swiftVersion. This doesn’t work for the UTF-8 string encoding employed in Swift 5, so Swift 5 replaces encodedOffset with utf16Offset(in:) to handle both cases [SE-0241]:


let swiftVersion = "Swift 5"
let offset = swiftVersion.endIndex.utf16Offset(in: swiftVersion)

 

New Pointer Methods

 

Swift 5 adds withContiguousStorageIfAvailable(_:) to Sequence and withContiguousMutableStorageIfAvailable(_:) to MutableCollection to provide generic implementations for withUnsafeBufferPointer(_:) and withUnsafeMutableBufferPointer(_:) in protocol extensions [SE-0237].

 

SIMD Vector Updates

 

Swift 5 adds operations on SIMD types for processors to the standard library. They provide low-level support for SIMD vectors and matrices. They also simplify the Objective-C, C and C++ implementations of <simd/simd.h> [SE-0229].

 

Where to Go from Here?

 

You can download the final playground using the Download Materials link at the top or bottom of this tutorial.

 

Swift 5 adds many cool features to Swift 4.2 and makes the language ABI stable. This is an important milestone in the language’s evolution, since there will be fewer changes from now on.

 

Let's sculpt your dreams into code. Consult with us to bring your vision to life!

Facebook Linkedin