Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Swift Cookbook

You're reading from   Swift Cookbook Proven recipes for developing robust iOS applications with Swift 5.9

Arrow left icon
Product type Paperback
Published in Jun 2024
Publisher Packt
ISBN-13 9781803239583
Length 422 pages
Edition 3rd Edition
Languages
Tools
Arrow right icon
Authors (4):
Arrow left icon
Chris Barker Chris Barker
Author Profile Icon Chris Barker
Chris Barker
Daniel Bolella Daniel Bolella
Author Profile Icon Daniel Bolella
Daniel Bolella
Nathan Lawlor Nathan Lawlor
Author Profile Icon Nathan Lawlor
Nathan Lawlor
Keith Moon Keith Moon
Author Profile Icon Keith Moon
Keith Moon
Arrow right icon
View More author details
Toc

Table of Contents (15) Chapters Close

Preface 1. Chapter 1: Swift Fundamentals 2. Chapter 2: Mastering the Building Blocks FREE CHAPTER 3. Chapter 3: Data Wrangling with Swift 4. Chapter 4: Generics, Operators, and Nested Types 5. Chapter 5: Beyond the Standard Library 6. Chapter 6: Understanding Concurrency in Swift 7. Chapter 7: Building iOS Apps with UIKit 8. Chapter 8: Building iOS Apps with SwiftUI 9. Chapter 9: Getting to Grips with Combine 10. Chapter 10: Using CoreML and Vision in Swift 11. Chapter 11: Immersive Swift with ARKit and Augmented Reality 12. Chapter 12: Visualizing Data with Swift Charts 13. Index 14. Other Books You May Enjoy

Async/Await in Swift

Starting with Swift 5.5, we were introduced to yet another, though helpful, way to write and perform asynchronous code using Async/Await. For anyone who has worked in JavaScript or C# before, this may seem familiar and welcomed. What Async/Await does is allow us to write async functions just like we would any other synchronous code, and then call them using the await keyword.

In this recipe, we will take our PhotobookCreator app and swap out how we use Dispatch for Async/Await, highlighting some of the advantages it brings to Swift.

Getting ready

We will see how we can improve the responsiveness, readability, and safety of an app using Async/Await, so we will work with an improved version of our app. Go to https://github.com/PacktPublishing/Swift-Cookbook-Third-Edition/tree/main/Chapter%206/PhotobookCreator_AsyncAwait. Here, you will find the repository of an app that takes a collection of photos and turns them into a PDF photo book, but using Async/Await. You can download the app source files directly from GitHub or by using git:

git clone   https://github.com/PacktPublishing/Swift-Cookbook-Third-Edition/tree/main/Chapter%206/PhotobookCreator_AsyncAwait/PhotobookCreator

How to do it...

While using Dispatch, we were able to achieve asynchronous code, but there were a few inconveniences. We had to create DispatchQueue, pass in a completion handler, and ensure that we returned to the main queue to run our completion.

As we’ll see, Async/Await handles some of those details for us and makes much of what we’re trying to achieve more readable:

  1. First, in PhotoCollectionViewController, let’s refactor generatePhotoBook. Instead of taking a closure, let’s return what we would’ve passed into the handler: a URL. To signal that we’ll be running this asynchronously, let’s use the async keyword:
    func generatePhotoBook(with photos: [UIImage]) async -> URL {
         //...
    }
  2. Now, we’ll refactor the body of our function. Since async functions are made to look like synchronous functions, you’ll notice it reads very straightforward. However, we’ll add the await keyword in front of the calls we expect to be waiting on. Lastly, we’ll simply return the URL we’re looking for:
    func generatePhotoBook(with photos: [UIImage]) async -> URL {
         let resizer = PhotoResizer()
         let builder = PhotoBookBuilder()
               // Get smallest common size
         let size = await resizer.smallestCommonSize(for: photos)
         // Scale down (can take a while)
         var photosForBook = await resizer.scaleWithAspectFill(photos, to: size)
         // Crop (can take a while)
         photosForBook = await resizer.centerCrop(photosForBook, to: size)
         // Generate PDF (can take a while)
         let photobookURL = await builder.buildPhotobook(with: photosForBook)
         return photobookURL
          }
  3. The compiler will start complaining that we’re awaiting non-async functions. Let’s fix this by simply adding the async keyword to each function we plan to call asynchronously:
    func smallestCommonSize(for photos: [UIImage]) async -> CGSize
    func scaleWithAspectFill(_ photos: [UIImage], to size: CGSize) async -> [UIImage]
    func centerCrop(_ photos: [UIImage], to size: CGSize) async -> [UIImage]
    func buildPhotobook(with photos: [UIImage]) async -> URL
  4. Lastly, we update our caller, generateButtonPressed. Since it is IBAction, which is synchronous, we cannot use the async keyword. Instead, we’ll wrap our asynchronous code using a Task closure:
    @IBAction func generateButtonPressed(sender: UIBarButtonItem) {
         activityIndicator.startAnimating()
         Task {
                    let photobookURL = await generatePhotoBook(with: photos)
              activityIndicator.stopAnimating()
              let previewController = UIDocumentInteractionController(url: photobookURL)
              previewController.delegate = self
              _ = previewController.presentPreview(
    animated: true)
         }
    }

How it works...

A huge advantage of using Async/Await is that it allows us to clearly mark code that will be used asynchronously (using async) and that we must wait for before moving on (using await).

This is exceptionally helpful when looking at the body of generatePhotoBook. First, there’s no need for any closure since the entire function has been marked as asynchronous. Next, we can clearly see the steps our code will be executing by taking note of where await appears. Lastly, we simply return the value our caller wants to use.

Returning a value instead of calling a closure (specifically on the main thread as we do using Dispatch) introduces not only better readability but also safety. It puts the responsibility of the callers’ code back where it belongs: in the hands of the caller itself.

We achieve this in generateBackButtonPressed. Yes, we still end up using a closure, specifically a Task closure. However, we use it to simply signify that we want to execute an asynchronous call, as opposed to packaging up and sending off our code to be called and executed elsewhere:

Task {
     let photobookURL = await generatePhotoBook(with: photos)
     activityIndicator.stopAnimating()
     let previewController = UIDocumentInteractionController(url: photobookURL)
     previewController.delegate = self
     _ = previewController.presentPreview(animated: true)
}

We simply await the call to generatePhotoBook, soundly trusting that once it completes execution, we can safely move forward with updating our UI with a URL.

The syntax changes between using Dispatch and Async/Await provide a dramatic difference in readability, simplicity, and safety, even in the simple use case we used them in. We’ve only scratched the surface of the capabilities offered by Async/Await.

See also

Documentation relating to Async/Await: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/

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 $19.99/month. Cancel anytime
Banner background image