Swift New Concurrency Framework (Part 2)
It is mandatory to read part1 before continue this , OR if you have idea how async/await basic implementation
As shown in Figure 1 we created a simple generic Network Manager to perform network request just pass model you expect in response and it in return fill that model , the magic Codable
do normally, You have situation like NetworkManager in written in third party framework and they are very lazy to adopt new things so you can’t change their code
In Figure 2 you see how our code is using completion handler and we have minimum deployment is ios13 and we have Xcode 13.2, so our developers are very excited to use async-await because of it;s great benefits we saw in part1. The only problem is we can’t change NetworkManger class .Inshort client want to use new SDK
Continuations to convert completion handlers into async functions
- Swift uses continuations to solve this problem, allowing us to create a bridge between older functions with completion handlers and newer async code.
- As you can we have
getFirstTodo
older method that uses completion handler , we created async alternativefirstTodo
that internally calling our completion signature method and uses continuations to make a bridge , In short we refactor with no major impact on our older code , it might possible some will callgetFirstTodo
and new code will callfirstTodo
- Swift provides us with
continuations
, which are special objects we pass into the completion handlers as captured values. Once the completion handler fires, we can either return the finished value, throw an error, or send back a Result that can be handled elsewhere. - As you can see, starting a continuation is done using the
withCheckedContinuation
() function, which passes into itself the continuation we need to work with. It’s called a “checked” continuation because Swift checks that we’re using the continuation correctly, which means abiding by one very simple, very important rule: “Your continuation must be resumed exactly once. Not zero times, and not twice or more times — exactly once.: - By using continuation you observe we can make async alternative of our completion block method code , now callers can use it using async/await syntax
- When we call
firstTodo
async function , it internally callgetFirstTodo
which have completion block . We need to return the result from the callback back to the places awaiting calls to the asyncfirstTodo
function. Not only that, those callers are in a suspended state. We need to make sure to resume them at the right point in time and with the right data so that they can get on with the rest of their work. - In
part1
we showed you how Swift and the system cooperate to take care of suspend and resuming async code for us. The question is how in this case suspend/resume process works - When the async version of
firstTodo
it then callsgetFirstTodo
which from network fetch todo. At some later time, Network calls the completion handler and passes the result . This situation looks almost identical to the one we previously showed you earlier when ourAsynchronousFunctionOne
function asked the system, not Network, to resume a suspended async function call. (This things is important here when we discuss continuation) - All that’s missing is a bridge to await the completion handler and resume with the
getFirstTodos
results. - This pattern comes up all the time, and it has a name: a continuation. : (methods that take completion blocks).
- The caller of the method awaits the result of the function call and provides a closure to specify what to do next. When the function call completes, it calls the completion handler to resume whatever the caller wanted to do with the result. This kind of cooperative execution is exactly the way async functions in Swift work.
return await withCheckedContinuation { [weak self] (continuation: CheckedContinuation<Result<ToDo, Error>
with this line it’s same like await we do but previous resume was done automatically , in our case with continuation the second link which is resume is to be done manually , Both case thread will suspened one or more time or never suspend A/C to system level - The
withCheckedContinuation
function lifts completion blocks without errors Swift functions. It has a counterpart calledwithCheckedThrowingContinuation
for the situations where you know a function will throw an error. These functions are the way to gain access to a continuation value you can use to resume a suspended async function. This also builds the first part of the bridge by allowing us to await calls togetFirstTodo
. - Let’s finish building the bridge. The continuation value provides a resume function into which we place the results from the completion handler. Not only that, but resume provides the missing link we need to unsuspend any calls awaiting the result of the persistentPosts function. And there, in one neat package, is our finished bridge from completion handlers to async functions.
Continuation Keep in Mind
Continuations provide a powerful way to manually take control over the execution of an async function, but there are some things to keep in mind.
- Continuations have a simple but important contract. Resume must be called exactly once on every path. But don’t worry. Swift has your back here.
- If the continuation is discarded without resume being called, the Swift runtime will log a warning since this will result in async calls never unsuspending as shown in Figure 5
- If a continuation is resumed multiple times in the same function, however, this is a more serious error as it can corrupt program data. To combat this, the Swift runtime will detect attempts to call resume multiple times and will ensure a fatal error occurs at the second resumption point as shown in Figure 6
- Many APIs are event driven. They provide delegate callbacks to notify our application at specific critical points and allow it to respond appropriately. In order to properly adopt async/await, we’ll have to store the continuation and resume it later.
- As before, we create a checked continuation.Then we save it and kick off the work.To respect the API contract of checked continuations, we make sure to resume the active continuation, and finally nil it out so we’re protected from calling it more than once.
- Always remember: the checked continuation value here represents the ability to manually resume any async calls to this API, so it must be called on all paths.
- If your delegate API is called many times or not at all in certain circumstances, it is critical to resume any active continuations exactly once.
Recap await
await
is where the magic happens. Whenever the program finds the await keywords, it has the option of suspending the function. It may or may not do so, and the decision is up to the system.- If the system does suspend the function,
await
will return control, not to the caller, but to the system. The system will then use the thread to perform other work until the suspended function is done. The statements belowawait
will not be executed until it has finished. The system decides what’s important to execute, and at some point, it will return control back to you after it sees theawait
ed function has finished. - You can think of it as a traffic light. If you are driving down the road and you find a red light, chances are you will stop. But if it is 4 AM in the morning and there’s no cars coming you may just run it. ***
What you need to understand about await is that, if it does choose to suspend, nothing below it will execute until the system tells it to, and the system will use the thread to do other work.Every call to an
async
function, must be marked asawait
.One final important note about
await
: It’s not guaranteed that the same thread that executed the code above it is the same one that will execute the code below it (commonly called the continuation).
Continuation Summary
- A continuation is simply what happens after an async call. When you are using async/await, the continuation is easy to understand: Everything below an
await
call, is a continuation. - In this example, the keyword
await
will (may) trigger a data download task in a different thread. Everything underneathawait
(that is, starting on the line with aguard
), is a continuation. on Code1 - Continuations are not limited to the async/await APIs. When you are using closure-based async APIs, a continuation is everything called within your completion handlers.
This is a closure version of the code above. Once again, the continuation starts at the guard
. The main difference is the completion handler version has a flow that is harder to follow.
- Swift provides a few methods we can use to convert callback-based code into async/await:
withCheckedContinuation
andwithCheckedThrowingContinuation
. The difference between the two is the latter is used for code that throws errors. I call these methods explicit continuations.: - If you wanted to start your async/await migration with this method, the simplest way to do it would be by wrapping a call to
downloadImageAndMetadata(for:imageNumber:completionHandler)
inside thewithCheckedThrowingContinuation
method. - The magic behind this function occurs inside the
withCheckedThrowingContinuation
part. This function will give us aCheckedContinuation<T, E> where E: Error
object that provides us with methods we need to call. In this example, the original version ofdownloadImageWithMetadata
passes us aDetailedImage
or an error, and we need to call the rightresume
method depending on what we get. If this method called us with aResult<DetailedImage, Error>
, we could call.resume(with:)
and pass it theresult
directly. - Continuations must be called exactly once, therefore there must be a continuation call within every branch of
withCheckedThrowingContinuation
. If you forget to call.resume
, things could go awry. Luckily, Swift will let you know.
Continue With Our Todo
As shown in Figure 7 we created withCheckedThrowingContinuation and now function throw error as well
Useful Links
https://developer.apple.com/videos/play/wwdc2021/10132/?time=1957