Disclaimer — This is the continuation of the ‘async-await’ article that I have written previously. If you have not read that then I would suggest you check that article first. Also information presented in this artilce is gather after watching the “Meet async-await in Swift” WWDC 21 video.
Note — I am planning to write more on this topic and this article will be update accordingly in the future.
Let’s start with a question that someone asked me last week
Question — Can we replace “Task” block with “DispatchQueue.global(). async”?
Answer — A shot answer is ‘No’ but let’s see why…
- Functions marked with ‘async’ keyword are different than normal function because unlike other functions they are allowed to suspend.”- —
- DispatchQueue.global(). async” are not designed to handle functions marked as ‘async’.
Calling async function from a synchronous context
If we will try to call an asynchronous function (marked with ‘async’ keyword) from a synchronous context then complier gives the following error
Why we get this error?
Because ‘.onAppear(perform: …)’ modifier is not designed to handle ‘async’ functions.
Fix for above error
Why to switch to async — await?
Comparison between asynchronous functions with completion handlers and asynchronous functions marked with ‘async’ keyword.
This comparison is based on following criteria
- Control flow
- Lines of code required
- Error handling support
Functions using Completion Handlers
Things to notice
- Control flow is jumping back and forth.
- Swift can’t check our work i.e. native error handling can’t be used with completion handlers.
- Requires more lines of code.
Functions marked as async
Things to notice
- Control flow is Linear.
- Enables the use of native error handling.
- Code is concise as compared to completion handler approach.
So, to answer the question — “Why to switch to async-await”?
Because it makes
- Code linear
- Enables error handling
- Code concise
How to? — Convert an asynchronous function with completion handlers to async-await syntax
Replace all the completion handlers in the calling hierarchy into async-await syntax
Note — Following steps are written considering the following example and may not be applicable for all the cases.
- Replace completion handlers with async keyword.
- If completion handler has “Error” as one of its parameters then add ‘throws’ keyword after ‘async’ keyword.
- Except “Error” parameter, make other parameters, return type of the async function.
- Replace all the asynchronous function calls with completion handlers with their ‘async’ counterpart.
- If the asynchronous function (with completion handlers) is provided by some framework then most probably an ‘async’ version of the function will already be available (iOS 15 and above). So, look for it and use it instead of the current one.
- But if the asynchronous function (with completion handlers) is something that you have written then it can be changed to ‘async’ function using Step 1.
- Replace all the completion handlers call backs with either returning a value or by throwing an error.
Asynchronous functions using Completion Handlers
Asynchronous functions marked as async
Note — Compare “Asynchronous functions using Completion Handlers” code snippet with “Asynchronousfunctions marked as async” code snippet based on control flow, error handling support and conciseness and you get the answer of the question — “Why to switch to async-await?”
Problem with Approach 1:
We have to convert all the functions in the hierarchy to an ‘async’ syntax which could easily become a time consuming and error prone task.
Consider following example
In the above example, to convert ‘execute (…)’ function to an ‘async’ syntax, we have to convert all the functions within the scope of ‘execute (…)’ function to an ‘async’ syntax, which itself will be a very cumbersome task.
Solution — Approach 2:
Mixing ‘async’ functions with completion handlers
- Since this is an ‘async’ function, the caller of ‘execute (…)’ function will be in a suspended state.
- The completion block will be executed at some point in the future but exactly when we are not sure about.
- There is no way to synchronize the communication between the execution of the completion handler and resuming of suspended callers of ‘execute (…)’ functions.
To fix this problem — we have now following functions available in swift 5.5
- withCheckedContinuation<T> (…)
- withCheckedThrowingContiniuation<T> (…)
Solution — Use ‘checked Continuation’
Note — Compare “Asynchronous functions using Completion Handlers” code snippet with “Asynchronousfunctions marked as async + checked continuation” code snippet based on control flow, error handling support and conciseness and you get the answer of the question — “Why to switch to async-await?”
How to? — Converting event based asynchronous functions to async-await syntax
Problem with the current event-based delegation approach is again same
- Nonlinear control flow
- Swift can’t check our work — No error handling Support
- More line of code is required as compared to async-await version of the same implementation.
Solution — Use ‘checked Continuation’
Note — Compare “Asynchronous functions with event-based delegation” code snippet with “Asynchronous functions marked as async” code snippet… You know the drill now, right? 😅
“Checked continuations” — Points to remember
- Continuations must be resumed exactly once on every path.
- Discarding the continuation without resuming is not allowed.
- Swift will check your work.
That’s it for now, thanks for reading. Also if you really find this helpful then please leave some applause to this article.😀😀😀