Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Mastering iOS 14 Programming

You're reading from   Mastering iOS 14 Programming Build professional-grade iOS 14 applications with Swift 5.3 and Xcode 12.4

Arrow left icon
Product type Paperback
Published in Mar 2021
Publisher Packt
ISBN-13 9781838822842
Length 558 pages
Edition 4th Edition
Languages
Tools
Arrow right icon
Authors (3):
Arrow left icon
Mario Eguiluz Alebicto Mario Eguiluz Alebicto
Author Profile Icon Mario Eguiluz Alebicto
Mario Eguiluz Alebicto
Chris Barker Chris Barker
Author Profile Icon Chris Barker
Chris Barker
Donny Wals Donny Wals
Author Profile Icon Donny Wals
Donny Wals
Arrow right icon
View More author details
Toc

Table of Contents (22) Chapters Close

Preface 1. Chapter 1: What's New in iOS 14? 2. Chapter 2: Working with Dark Mode FREE CHAPTER 3. Chapter 3: Using Lists and Tables 4. Chapter 4: Creating a Detail Page 5. Chapter 5: Immersing Your Users with Animation 6. Chapter 6: Understanding the Swift Type System 7. Chapter 7: Flexible Code with Protocols, Generics, and Extensions 8. Chapter 8: Adding Core Data to Your App 9. Chapter 9: Fetching and Displaying Data from the Network 10. Chapter 10: Making Smarter Apps with Core ML 11. Chapter 11: Adding Media to Your App 12. Chapter 12: Improving Apps with Location Services 13. Chapter 13: Working with the Combine Framework 14. Chapter 14: Creating an App Clip for Your App 15. Chapter 15: Recognition with Vision Framework 16. Chapter 16: Creating Your First Widget 17. Chapter 17: Using Augmented Reality 18. Chapter 18: Creating a macOS app with Catalyst 19. Chapter 19: Ensuring App Quality with Tests 20. Chapter 20: Submitting Your App to the App Store 21. Other Books You May Enjoy

Introducing Swift 5.3

Introduced by Apple during 2020, the main goal in Swift 5.3 is to enhance quality and performance and to expand the number of platforms on which Swift is available by adding support for Windows and additional Linux distributions.

Now, let's review some of the new language features.

Multi-pattern catch clauses

With this new feature, Swift will allow multiple error-handling blocks inside a do catch clause. Take a look at the following example.

Imagine that we have a performTask() function that can throw different types of TaskError errors:

enum TaskError: Error {
  case someRecoverableError
  case someFailure(msg: String)
  case anotherFailure(msg: String)
}
func performTask() throws -> String {
  throw TaskError.someFailure(msg: "Some Error")
}
func recover() {}

Prior to Swift 5.3, if we want to handle different TaskError cases inside a do catch block, we need to add a switch statement inside the catch clause, complicating the code, as follows:

do {
  try performTask()
} catch let error as TaskError {
  switch error {
  case TaskError.someRecoverableError:
    recover()
  case TaskError.someFailure(let msg),
       TaskError.anotherFailure(let msg):
    print(msg)
  }
}

Now Swift 5.3 allows us to define multiple catch blocks so we can make our code more readable, as in the following example:

do {
  try performTask()
} catch TaskError.someRecoverableError {
  recover()
} catch TaskError.someFailure(let msg),
        TaskError.anotherFailure(let msg) {
  print(msg)
}

We no longer need the switch inside the catch block.

Multiple trailing closures

Since the beginning of Swift, it has supported trailing closures syntax. See this classic example when using the UIView.animate method:

UIView.animate(withDuration: 0.3, animations: {
  self.view.alpha = 0
}, completion: { _ in
  self.view.removeFromSuperview()
})

Here, we were able to apply the trailing closure syntax to the completion block to make our code shorter and more readable by extracting completion from the parentheses and removing its label:

UIView.animate(withDuration: 0.3, animations: {
  self.view.alpha = 0
}) { _ in
  self.view.removeFromSuperview()
}

This closure syntax has some side-effects too. It can make our code hard to read if a developer is not used to our methods (think about our own API library that is not as well known as UIKit methods). It also makes the code a bit unstructured.

With Swift 5.3, when we have multiple closures in the same method, we can now extract and label all of them after the first unlabeled parameter:

UIView.animate(withDuration: 0.3) {
  self.view.alpha = 0
} completion: { _ in
  self.view.removeFromSuperview()
}

Notice how now we have both closures outside of the parentheses, UIView.animate(withDuration: 0.3). Also notice how labeling the completion method makes it easier to understand, and how the code now looks more symmetrical in terms of structure, with all the closures written in the same way.

Synthesized comparable conformance for enum types

Swift 5.3 allow enum types with no associated values or with only Comparable values to be eligible for synthetized conformance. Let's see an example. Before Swift 5.3, if we wanted to compare the values of an enum, we needed to conform to Comparable, and we needed to implement < and minimum methods (among other ways to achieve this):

enum Volume: Comparable {
    case low
    case medium
    case high
    private static func minimum(_ lhs: Self, _ rhs: Self) -> Self {
        switch (lhs, rhs) {
        case (.low,    _), (_, .low   ):
            return .low
        case (.medium, _), (_, .medium):
            return .medium
        case (.high,   _), (_, .high  ):
            return .high
        }
    }
    static func < (lhs: Self, rhs: Self) -> Bool {
        return (lhs != rhs) && (lhs == Self.minimum(lhs, rhs))
    }
}

This code is hard to maintain; as soon as we add more values to the enum, we need to update the methods again and again.

With Swift 5.3, as long as the enum doesn't have an associated value or it only has a Comparable associated value, the implementation is synthesized for us. Check out the following example, in which we define an enum called Size, and we are able to sort an array of Size instances (without any further implementation of Comparable methods):

enum Size: Comparable {
  case small(Int)
  case medium
  case large(Int)
}
let sizes: [Size] = [.medium, .small(1), .small(2), .large(0)]

If we print the array with print(sizes.sorted()), we will get this in the console:

[.small(1), .small(2), .medium, .large(0)]

Note how the order of sorting is the same as the order in which we define our cases, assuming it is an increasing order: .small appears before .large when we sort the values. For instances of the same case that contain associated values (such as .small(Int) and .large(Int)) we apply the same principle when ordering: .small(1) appears before .small(2).

Increase availability of implicit self in escaping closures when reference cycles are unlikely to occur

Sometimes the rule that forced all uses of self in escaping closures to be explicit was adding boilerplate code. One example is when we are using closures within a Struct (where the reference cycle is unlikely to occur). With this new change in Swift 5.3, we can omit self, like in this example:

struct SomeStruct {
    var x = 0
    func doSomething(_ task: @escaping () -> Void) {
      task()
    }
    func test() {
      doSomething { 
        x += 1 // note no self.x
      }
    }
}

There is also a new way to use self in the capture list (just by adding [self] in) when needed so that we can avoid using self again and again inside the closures. See the following example:

class SomeClass {
    var x = 0
    func doSomething(_ task: @escaping () -> Void) {
      task()
    }
    func test() {
      doSomething { [self] in
        x += 1 // instead of self.x += 1
        x = x * 5 // instead of self.x = self.x * 5
      }
    }
}

This change reduces the use of self in many situations and omits it completely when it is not needed.

Type-based program entry points – @main

Up until now, when developing a Swift program (such as a terminal app), we needed to define the program startup point in a main.swift file. Now we are able to mark a struct or a base class (in any file) with @main and a static func main() method on it, and it will be triggered automatically when the program starts:

@main
struct TerminalApp {
    static func main() {
        print("Hello Swift 5.3!")
    }
}

Important note

Take into consideration the following about @main: it should not be used if a main.swift file already exists, it should be used in a base class (or struct), and it should only be defined once.

Use where clauses on contextually generic declarations

We can use where clauses in functions with generic types and extensions. For example, look at the following code:

struct Stack<Element> {
    private var array = [Element]()
    mutating func push(_ item: Element) {
        array.append(item)
    }
    mutating func pop() -> Element? {
        array.popLast()
    }
}
extension Stack {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}

We constrained the sorted() method on the extension of this Stack struct to elements that are Comparable.

Enum cases as protocol witnesses

This proposal aims to lift an existing restriction, which is that enum cases cannot participate in protocol witness matching. This was causing problems when conforming enums to protocol requirements. See the following example of a protocol that defines a maxValue variable:

protocol Maximizable {
    static var maxValue: Self { get }
}

We can make Int conform to Maximizable like this:

extension Int: Maximizable {
  static var maxValue: Int { Int.max }
}

But if we try the same with an enum, we will have compile issues. Now it is possible to do this:

enum Priority: Maximizable {
    case minValue
    case someValue(Int)
    case maxValue
}

This code now compiles properly with Swift 5.3.

Refine didSet semantics

This is a very straightforward change, according to the Swift proposal:

  • If a didSet observer does not reference the oldValue in its body, then the call to fetch the oldValue will be skipped. We refer to this as a "simple" didSet.
  • If we have a "simple" didSet and no willSet, then we could allow modifications to happen in-place.

Float16

Float16 has been added to the standard library. Float16 is a half-precision (16b) floating-point value type. Before Swift 5.3, we had Float32, Float64, and Float80.

In this section, you have learned about the latest additions to the language in Swift 5.3 with code examples. Now, let's finish with the chapter summary.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime
Visually different images