await for a new async in Swift
iOS Swift await/async Estimated reading time: 7 minutesAsync tasks allow us to improve UX and use (or at least try to use) all possible power that the device could provide for us. Almost every app nowadays uses async code - from executing small, not important to heavy, possibly remote, tasks. Such behavior can greatly improve any flow and move u’r app to the next level.
The problem
Async code with callbacks provides great power to us (such as run some part of code on another thread or auto handle completion of async code, or even the possibility to provide non-blocking behavior). However, with this power comes great irresponsibility - our code can have a lot of bad things like:
- pyramid of doom (A sequence of simple asynchronous operations often requires deeply-nested closures)
- unclear Error handling (Callbacks make error handling difficult and very verbose)
- errors in logic (sometimes callback calls can be missed in nested conditional flows - when use guard for example)
- poor maintenance, scalability, understanding, and readability
- hard conditional execution
We have a lot of techniques to fix these issues:
- shallow code
- pack u’r code into modules
- use the monad-like style for error/data handling
- limit nesting functions calls
- reuse code
- Result type
For now, we have a few techniques that are used almost on an every-day basis - GCD and OperationQueue. And they are great - easy to use, has a lot of possibilities, very flexible, has reach documentation, but… we still should remember about the problems that were mention before, and so, use additional techniques for resolving them.
This problem isn’t new. And in other languages there is a possible solution - async/await. For example - in c#:
provides an abstraction over asynchronous code. You write code as a sequence of statements, just like always. You can read that code as though each statement completes before the next begins. The compiler performs several transformations because some of those statements may start work and return a Task that represents the ongoing work. (from the official doc).
Async/await
Recently, new proposal for Swift appears.
Interesting. As for me - this looks like one of the biggest improvements during the last time for Swift language.
Let’s start from _Concurrency
- this is an experimental framework, and so we can’t use it for any production code (API may be changed), but for a test, this works just fine.
Task
Here, we can find Task
- a type that represents some kind of work. From doc - ” is the analog of a thread for asynchronous functions. All asynchronous functions run as part of some task.”
With this Task
type we can run code using various options:
runDetached
- run as is (not recommended)withDeadline
- execute a task with deadline restrictionwithGroup
- by grouping a few tasks
In the declaration of these functions u can find a new attribute -
@concurrent
. I found the interesting thread on Swift forum related to this, also there is a note about this attribute - “The purpose of@concurrent
is to support function values that conform toConcurrentValue
and can therefore be used in contexts that requireConcurrentValue
.”
ConcurrentValue
- this is protocol, that mark object as safe and ready for concurent operations (“are safe to share across concurrently-executing code” according to proposal).
Task
also has supportive functions and properties such as currentPriority
or cancel
/sleep
etc. I believe this API will be extended to allow do same things as with GCD
and OperationQueue
.
Actor
Another interesting type there - Actor
- “An actor is a form of class that protects access to its mutable state” (source).
There is not much about this type in the current API, I believe it will be extended in the few next updates.
Convert functions into new async code
The last part, that needs to be mentions - is support for existing code. If u already has some asynchronous code with closure-based callback, we can use few functions for converting it to a new async way:
Entry point
As mention in proposal “Because only async code can call other async code, this proposal provides no way to initiate asynchronous code. This is intentional: all asynchronous code runs within the context of a “task”” (source).
This entry point may be an available function
Practice
Better - is always to test the code - run it and feel the power :].
Environment
To test await/async we should install beta toolchain from here. Just scroll to Trunk development (main) and download the latest version. Then install the package and switch to it in xCode settings.
If u do everything fine, then u can see blue chain link in status bar of xCode project:
To use async/await in a project u have few options - use it in swift package or in the project. In both cases, u should add -Xfrontend -enable-experimental-concurrency
.
Put it in Other Swift flags
in build settings for a project.
For SP, for selected target add:
Code sample
The easiest variant - to use Task, that can be run in place:
We can define next function:
If we try just to run it we will get an error:
Even with async
from Dispatch
:
As I mention above, we need to get an entry point for the async code.
We can either:
or
Both will run just fine and provide the next output:
Now, case when we want to reuse existing code:
and result is:
What, if we want to use result of some async
function after await
? Use @asyncHandler
:
output:
@asyncHandler
functions cannot be marked asasync
Conclusion
As for me, this is a great improvement, that provides a better and more convenience way of async code handling. Can’t await when it to become available :].
Resources
- Async/await for Swift
- Async-await experiment
- Toolchain download page
- Using async/await in SwiftUI
- Task API and Structured Concurrency
- Concurrency Interoperability with Objective-C
ConcurrentValue
- SE-0296: async/await SF
- Getting started with async/await in Swift
- Simple example involving structured concurrency
- Pitch #4: ConcurrentValue and @concurrent closures Evolution Pitches
Share on: