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

Note: the current version is Swift 5, iOS 12, Xcode 10. If you want to reprint this article, please contact the author and give the source address of the article

In this tutorial, you’ll learn about the most important changes in Swift 5. Swift 5 requires Xcode 10.2, so make sure you have it installed before you start.

An introduction to

Swift 5 is compatible with Swift 4.2, but not with earlier Swift versions. However, future versions will be compatible with Swift 5 binaries due to ABI stability.

ABI stability supports binary compatibility between applications and libraries compiled with different versions of Swift. The Swift standard library and runtime are embedded in the operating system, so applications do not distribute copies of their libraries on any platform. This leads to better tool decoupling and OS integration.

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

You will find Swift Evolution suggested numbers like **[SE-0001]** in each section of this tutorial. You can browse each proposal link to learn more about each new change.

The best way to follow this tutorial is to try out the new features on the playground.

Launch Xcode 10.2 and select File ▸ New ▸ Playground. Set the platform to iOS and the template to Blank. Name it and save it wherever you want to start it now!

Note: Need a quick reminder of Swift 4.2 highlights? See Swift 4.2 Tutorial: What’s new in Swift 4.2?

Language improvement

Swift 5 has many language features, such as dynamic callable types, handling future enumerations, and so on.

Integer Multiple detection

In Swift 4.2, you can use the remainder operator to determine if a number is a multiple of another:

let firstNumber = 4
let secondNumber = 2
ifsecondNumber ! =0 && firstNumber % secondNumber == 0 {
  print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")}Copy the code

How this code works:

  1. Check if secondNumber is 0.
  2. Check that dividing firstNumber by secondNumber returns the remainder of 0.
  3. Perform division.

You must check that secondNumber is 0 because the % operator throws an error.

Swift 5 simplifies this process by adding isMultiple(of 🙂 to BinaryInteger SE-0225:

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

IsMultiple (of 🙂 will work even if you pass the argument to 0, resulting in cleaner code.

Avoid escaping strings

Swift 4.2 uses escape sequences to represent backslashes and quotes 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. ""Copy the code

Swift 5 adds a new way to escape strings. You add # at the beginning and end of the string so that you can use backslashes and quotes without problems. 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. """#Copy the code

When using string interpolation in raw strings, a # sign must be used after a backslash:

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

In some cases, 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."# #Copy the code

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

In Swift 4.2, you can escape backslashes in 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)! ] )}Copy the code

Here’s how this code works:

  1. Declare the version and define the scope to cover the entire string.
  2. Defines a regular expression that matches all minor Swift versions in the version.
  3. usematches(in: options: range:)Determine the minor release scope.
  4. Use scope to get a minor version from a version.

Swift 5 simplifies regular expressions with raw strings:

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

In this code, you use half the number of backslashes to write your regular expression, because you don’t need to escape backslashes in the original string.

Note: Need more details on how regular expressions work in Swift? Check out the Regular Expressions tutorial: Introduction to Regular Expressions.

Use the Character new property

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

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

In this code, each character is first converted to a String and then to an Int to determine the number of digits in the ID.

However, Swift 5 adds attributes to Character to make it easier for the Character to use SE-0221:

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

In this case, you use isNumber to check that each character is a number. View proposals for additional properties that you can use.

Use the new Unicode scalar property

In Swift 4.2, you implemented a text processing algorithm 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.")
Copy the code

In this code, you can count the number of letters of a user name by checking whether the Unicode scalar for each character represents a lowercase or uppercase letter.

Swift 5 adds attributes for Unicode scalars, simplifying text handling SE-0211:

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

In this code, you use isAlphabetic to check whether each character is a number. The linked proposal shows all the properties you can check.

Deletion subsequence

Swift 4.2 returns the SubSequence from the Sequence custom point, 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)
Copy the code

In this case, if s is an Int or the last element, remove(_ 🙂 removes the last n elements in the sequence.

Swift 5 replaces SubSequence SE-0234 with the specific type in the sequence:

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

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

The dictionary update

Swift 5 brings long-awaited improvements to the dictionary:

Compression dictionary

Swift 4.2 uses mapValues, filter, and reduce to filter nil values in the dictionary, 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) }
Copy the code

This code uses mapValues with filter or Reduce to determine a student’s valid grade. Both methods require multiple dictionary passes and complicate the code.

Swift 5 uses compactMapValues(_ 🙂 for a more efficient solution SE-0218:

let mapStudents = students.compactMapValues(Int.init)
Copy the code

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

Rename DictionaryLiteral to KeyValuePairs

Swift 4.2 uses DictionaryLiteral to declare dictionaries, as shown below

let pets: DictionaryLiteral = ["dog": "Sclip"."cat": "Peti"]
Copy the code

DictionaryLiteral is not a dictionary or text. This is a list of key-value pairs.

Swift 5 renames DictionaryLiteral to KeyValuePairs SE-0214:

let pets: KeyValuePairs = ["dog": "Sclip"."cat": "Peti"]
Copy the code

Digital Protocol update

Swift 4.2 is the vector implementation value:

/ / 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))"}}Copy the code

Here’s how this code works:

  1. Declare x, y, and init(_: _:) for Vector.

  2. Init (integerLiteral:) in order to make the Vector accord with the requirement of ExpressibleByIntegerLiteral as digital consistency.

  3. Declare init(exactly:) and implement +(LHS: RHS :), +=(LHS: RHS :), -(LHS: RHS :), -=(LHS: RHS :), *(LHS: RHS :), *=(LHS: RHS :).

  4. Implement the description to make the Vector compliant with CustomStringConvertible.

The above code makes it easy to use vectors:

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)
Copy the code

Swift 5 implements AdditiveArithmetic of vectors because you cannot define the cross product se-0233 of 2D vectors. It does not require ExpressibleByIntegerLiteral consistency:

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
  }
}
Copy the code

In this code, you make the Vector conform AdditiveArithmetic by defining zero and implementing +(LHS: RHS :), +=(LHS: RHS :), -(LHS: RHS :), – =(LHS: RHS :).

Note: Want to learn more about operator overloading in Swift? See operator Reloading tutorial: Reloading custom operators in Swift.

String interpolation update

Swift 4.2 implements string interpolation by inserting 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)
Copy the code

In this code, the compiler packaging each text section first, and then use the init (insert a stringInterpolationSegment:). It then 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)
Copy the code

Here’s what the code does:

  1. Using a specific capacity and interpolation count definition DefaultStringInterpolation instance.
  2. callappendLiteral(_ :)orappendInterpolation(_ :)Add text and interpolation to the interpolation.
  3. By calling theinit(stringInterpolation :)Generates the final interpolation string.

Handle future investigation cases

Swift 4.2 cannot handle the new enumerated case properly, as shown 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."
Copy the code

Here’s what happens in the code above:

  1. Define all types of blog posts on your site.
  2. To make the switch infinite, add default values.
  3. Since screenshots and lessons are videos, they are processed by default.screencastand.course.

Here’s how processing 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."
Copy the code

In this code, you use the default for.podcast, even if the podcast is not a video. Swift 4.2 does not warn against this because the switch is exhaustive.

Swift 5 handles the added enumeration case 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."
Copy the code

In this code, you mark the default as **@unknown**, and Swift warns you that the switch is not exhaustive. Screencast,.course, and.podcast are handled by default, since screenshots of videos, lessons, and podcasts are blog posts.

Result

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
]
Copy the code

Here’s how this code works:

  1. Declare the most common connection errors.
  2. Compare join results and add them to the collection. You use these sets as dictionary keys because Result implements Equatable and Hashable.

Follow the Never class of Equatable and Hashable

Swift 5 fits 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
]
Copy the code

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

Dynamic callable type

Swift 5 defines a dynamic callable type SE-0216 that interoperates with scripting languages such as Python or Ruby:

/ / 1
@dynamicCallable
class DynamicFeatures {
  / / 2
  func dynamicallyCall(withArguments params: [Int]) -> Int? {
    guard! params.isEmptyelse {
      return nil
    }
    return params.reduce(0, +)}func dynamicallyCall(withKeywordArguments params: KeyValuePairs<String, Int>) -> Int? {
    guard! params.isEmptyelse {
      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
Copy the code

The code above looks like this:

  1. Mark DynamicFeatures as **@dynamicCallable** to make them dynamically callable.

  2. To make DynamicFeatures fit ** @dynamiccallable **, implement dynamicCallwithArguments 🙂 and dynamicCall(withKeywordArguments :).

  3. Using normal syntax to call functions, the compiler calls dynamicCall(withArguments 🙂 or dynamicCall(withKeywordArguments :).

Swift Package Manager update

Swift 5 adds some features to Swift Package Manager:

Platform Deployment Settings

Swift 5 allows you to define the minimum platform deployment target version required in package.SWIFT SE-0236:

let package = Package(the name:"Package", platforms: [macOS (. V10_14), the iOS (v12), the tvOS (v12), the watchOS. (v5)])Copy the code

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

Target build Settings

Swift 5 declares target-specific build Settings in package. Swift. They customize how the package manager invokes the build tool SE-0238 during the target build.

Relying on the mirror

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

swift package config set-mirror --package-url <package> --mirror-url <mirror>
Copy the code

Mirroring allows you to access dependencies even if the original source is unavailable or deleted.

Set-mirror uses mirror update dependencies, which replace all other mirrors.

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 --allCopy the code

Other improvements

Swift 5 also adds some other much-needed features and improvements:

Make coding ranges

Swift 5 adds conformance to Codable range SE-0239:

let temperature = 0.10
let encoder = JSONEncoder(a)let data = try! encoder.encode(temperature)
let decoder = JSONDecoder(a)let temperatureRange = try! decoder.decode(ClosedRange<Int>.self, from: data)
Copy the code

You encode the temperature using JSONEncoder and decode the data using JSONDecoder, because the range is Codable in Swift 5 by default.

Flatten the nested Optionals

Swift 4.2 Use **try? ** Create nested options:

extension Int {
  / / 1
  enum DivisionError: Error {
    case divisionByZero
  }
  
  / / 2
  func divideBy(_ number: Int) throws -> Int {
    guardnumber ! =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)}Copy the code

Here’s what the code does:

  1. Extend Int with DivisionError.
  2. divideBy(_ :)ifnumberfor0The thrown.divisionByZero.
  3. Unlock the partition twice as it is oneInt??.

Swift 5 handles the SE-0230 in a different way:

if let division = division {
  print(division)
}
Copy the code

Try to? In Swift 5 you don’t create nested options, so you need to unpack once because it’s an Int? .

Remove custom points from the Collection

You can access custom 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"
Copy the code

In this code, first the last name of the name is returned, and finally the first element of the array is returned.

Neither of the calculated attributes works as expected, so Swift 5 removes their custom points from the collection SE-0232.

Path of Identity Key

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 ă." ")
Copy the code

In this code, you change the title and author of the tutorial once, using.self.

Swift 5 added identity key path SE-0227 for value access:

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

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

Initialize by force

In Swift 5, the text initializer casts the text to its type if the type conforms to the text protocol SE-0213:

let value = UInt64(0xFFFF_FFFF_FFFF_FFFF)
Copy the code

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

Update to Swift version compilation

Swift 4.2 uses >= in compile 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
Copy the code

These criteria check if the Swift version is greater than or equal to 5 and compile the code when the criteria are met.

Swift 5 added < 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
Copy the code

Use the mutable parameters of enumerated cases with associated values

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

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

You can use String… Get tutorial and article details. This is not possible in Swift 5, so you should use arrays instead:

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

This time use **[String]** to set tutorial and article details.

Discards encoding offsets with string indexes

The Swift 4.2 string uses UTF-16 encoding. Therefore, encodedOffset will return the offset of the UTF-16 string:

let swiftVersion = "Swift, 4.2"
let offset = swiftVersion.endIndex.encodedOffset
Copy the code

Here you can get the endIndex offset in swiftVersion. This does not work with utF-8 string encoding used 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)
Copy the code

New pointer methods

Swift, 5 added ConttiguousStorageIfAvailable (_) to Sequence and withContiguousMutableStorageIfAvailable (_) to MutableCollection, For protocol extension of SE – 0237 withUnsafeBufferPointer (_) and withUnsafeMutableBufferPointer (_) provides a common implementation.

SIMD vector update

Swift 5 adds the processor’s SIMD-type operations 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

SE-0229.

And then where?

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

Swift 5 adds a lot of cool features to Swift 4.2 to make the language ABI stable. This is an important milestone in language development, because from now on, this change will be reduced.

You can read more about changes to this version of Swift at the official Swift CHANGELOG or Swift Standard Library Differences.

You can also check out the Swift Evolution proposal to see what changes are coming in the next Swift release. Here, you can provide feedback on proposals currently under review, or even submit your own proposals!

Project Example: Project address