August 12, 2021
January 3, 2022
As a developer, you will eventually write code that makes a call and takes some time to run. Network calls for example typically can take seconds to 10s of seconds to over a minute to run, in some extreme cases. There aren’t many instances when your users will want to wait that long for your app to respond and in an environment like a mobile device, the controlling operating system may think your app has crashed and shut it down. Threading and concurrency exist in most languages to handle this situation.
Concurrency is used to refer to the combination of asynchronous and parallel code. Asynchronous code allows for tasks to be suspended and resumed at a later point. This capability creates a more fluid user experience because it allows apps to complete short-term tasks like updating the UI while working on long-term tasks like retrieving data. Parallel code refers to multiple pieces of code running simultaneously. This gives users the ability to execute multiple tasks at the same time. The combination of asynchronous and parallel code, or concurrency, is essential to the function of modern mobile apps.
Announced in June 2021, at Apple’s annual WWDC conference, Swift 5.5 and iOS 15 include support for async/await concurrency. Swift currently has methods for handling concurrency, the most prominent being closures, delegates and Notification Center with registered observers. Let’s look at each of these for just a moment, all three work fine and fill needed requirements, but sometimes make for a poor substitute for async/await.
Closures, also known as completion handlers, are little functions that are defined within one function and can then be passed to another function to be called sometime later. Crystal clear, right? Not really, especially for newer programmers. In my own apps, I typically use closures for calling external Rest APIs. It allows me to unblock the main thread and make asynchronous calls for data I want to display. My standard app build includes a method with a closure for the data task at the lowest level of the call, a method with a closure for the specific API endpoint I’m calling and a method with a closure in the view controller that needs the data. Following the path between three methods down to the actual network call to the API and then unwinding the call back up through the closures is non-trivial, even after writing it myself. If I go too long without reading the code, I sometimes have to stop for a minute and really think through the steps again.
Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type. I typically use delegates for passing data between view controllers. In a split view controller app, where you have at least two view controllers visible on screen at the same time, you will run into scenarios where one controller needs to send data or notifications to the other. Making one a delegate of the other, or both delegates of each other allows this communication.
Notification Center, the third type of concurrency commonly used in Swift lets you set up something like a public message board in your app. This works in three steps: first you initialize the board for the specific message, second you add an observer in the controller that will eventually act on the message and third, a separate process will post to the board that an event has occurred. I’ve used this pattern recently when a view controller was depending on an Oauth handshake to first take place before it could fetch data from an API. Once an asynchronous task completed the authorization, it posted to the message board that the session was initiated, then the view controller that had already made itself an observer was notified by Notification Center and was then able to run its fetch method, which then ran through the three layers of closures listed above. 😬
All of these patterns can be used interchangeably, in many situations. Some situations fit one of the patterns better than others, but they work. I could use Notification Center for communicating between two controllers, but it’s cumbersome. Using closures for cross-controller communication would likely not work, so there is some crossover, but not complete. With the new async/await, you will mostly be using it for your calls to internal and external APIs, calls for data that take time to retrieve. Asynchronous functions, such as those using completion handlers, will become more readable and more correct when written in this new pattern with async/await.
Enough theory, let’s look at some example code for fetching and returning thumbnails for a UI.
Here’s the original version:
Here’s the new version with async/await applied:
Besides having a single indent level and only six lines of code to read, compared to twenty, the new code only has one exit point, the return of the thumbnail. If an error is encountered, the code will throw a sensical error. In the original version, there are five separate exit points to debug, test and track. Bugs are more easily hidden in complex code and code that’s difficult to read and follow won’t get as thorough of a code review. Code with multiple exit points like the original tend to surface those edge case issues, something the developer didn’t think of in design and is now causing a bug in production.
Completion handlers and closures are still with Swift and still have their place. Hopefully, more as a single-level request and response and not as a series of sequential steps. Async/Await and the other new concurrency tools like Asynchronous Sequences, Continuations and Actors bring a needed upgrade to Swift that will result in more readable, easily testable, less bug-prone code that just does what it says. Learn more by visiting the Swift Concurrency page on the Apple Developer portal. Apple has also published an article and WWDC video, all free for anyone, covering refactoring an app with async/await and other new, concurrency tools on the Apple Developer portal.
David is a Senior iOS Developer at Echobind. A long-time Apple fanboy, he has been building native apps since 2012 for iOS and even a few for Android. When not coding, which isn’t often, he’s exploring Arizona with his wife and their puppy Zoe. You can find him on Twitter @cybler.
January 3, 2022
Updated and curated content to get you started in the world of AI with Alexa Skills.
December 8, 2021