Swift New Concurrency Framework (Part 1)
Synchronous / Asynchronous Function
- In Synchronous function your thread is blocked and waiting for that function to be finish
- In Asynchronous function your thread is free to do other work when it’s done it will notify you by calling it’s completion handler OR you call function and then in return function call you at some point , but it’s not blocking
OR
An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk)
As shown in Figure 1 viewDidLoad
starts on main thread Then it calls synchronousFunction
until that function is executed completely main thread is busy on that function and you can see your thread is block , After that when synchronousFunction
complete its execution then it call AsynchronousFunction
and call all the synchronous
statement util it DispatchQueue.global().asyncAfter(deadline: .now()
+ where it create another thread and return immediately and before calling closure it call print(“After”) and after sometime it execute internal statement of the DispatchQueue.global(
closure which shows it don’t block the thread that is the reason it’s Asynchronous function and it’s completion mark with @escaping In example of an escaping closure is the completion handler. Depending on its implementation, a completion handler is typically executed in the future. A length task completes long after it was initiated, which means that the closure escapes and outlives the function it was created in.
There is no problem with the callbacks :p until you have only one . As shown in Figure 2 we have a dependent nested async task which we perform using callbacks and you will find two problems
- Callbacks pyramid of doom
- Control flow hell
Async / Await in Swift
As shown in Figure 3 we make code cleaner and easy to read
, these are all async function since it is using new structured concurrency of swift you can think and debug like you are working on synchronous function which is very helpful for the debugging purpose No, more callBack hell. There are lots of things to discuss and understand here , let’s jump
- Apple introduced the concept of async/await in Swift 5.5 and announced it in the WWDC21 session. Please note that async/await is only available in Swift 5.5 and Xcode 13. So please make sure to download latest Xcode version before proceeding with this tutorial
- Completion Handler, Delegate is
Unstructured
wheres asasync/await
is structured code why?async/await
construct follows the concept of structured concurrency. Meaning, for any code written usingasync/await
follows the structural sequential pattern unlike how closures work. For example, you might be calling a function passing the closure parameters. After calling the async function, the flow will continue or return. Once the async task is done, it will call closure in the form of a completion block.Here, the program flow and closure completion flow was called at different times breaking the structure, thus this model is called unstructured concurrency.
Structured concurrency makes code easier to read, follow and understand. Thus, Apple is aiming at making code more readable by adopting the concept ofasync/await
starting Swift 5.5. - When you mark the function async they keyword the keyword should go just before throws in the function signature or before arrow if function doesn’t throw
- Await after it suspend itself quickly unblocking the thread , the thread is then free to do other work
- You can see we used URLSession async function available, SDK’s has hundreds of thousands of awaitable methods available for your to use
When you comment out Task you see this Error async’ call in a function that does not support concurrency
which means viewDidLoad is not have async function in their method signature so it can’t use await . we cannot call it directly from synchronous
code. We will wrap it into Task
where it will execute parallelly. We will see in detail Task later part
+ve Point (Async / Await)
- Short code
- Safer (see Use async/await with URLSession section)
- structured code
- Make better reflect what’s your intend
- easier to read, follow and understand.
- more readable
- It makes your code linear, concise, and it supports native Swift error handling. (Use async/await with URLSession)
Synchronous Function Call
- When you call a Synchronous function you hand control of the thread (which is main in our case) your function (
viewDidLoad
) running on over to that function (SynchronousFunctionOne
). If it is a Synchronous function you are calling then thread will be fully occupy doing work on behalf of that function until it finishes
Async/Await Function Call (Asynchronous Structured Code)
- Just like a normal function, when you call an async function (
AsynchronousFunctionOne
), you give control of the thread (Main Thread )to it. - Once it’s running, an async function can suspend. When it does, it gives up control of the thread. But rather than giving control back to your function, it instead gives control of the thread to the system. When that happens, your function (
AsynchronousFunctionOne
) is suspended too. - Question is when you can see the function can suspend when you see the word await so in our case try await URLSession.shared.data(from: url) at that point function can suspend, This is also known as suspension point , Note when the function is suspend , that thread is free to do other work. Further try await URLSession.shared.data(from: url) this line will create another background thread as well
- Suspending is the function’s way of telling the system, “I know you have a lot of work to do. You decide what’s most important.” How cooperative is that? So once the function suspends itself, the system is free to use the thread to do other work. At some point, the system will decide that the most important work to be done is to continue running the async function that had suspended itself earlier. At that point, the system will resume it. That async function is then back in control of the thread and able to keep going about its work.
- And if it wants, it can suspend itself again. In fact, it can suspend itself as many times as it needs to.
- On the other hand, it may not need to suspend itself at all. While an async function may suspend, just because it’s marked async doesn’t necessarily mean that it will suspend. And by the same token, just because you see an “await” doesn’t mean the function will definitely suspend there. But eventually, whether without ever suspending or after resuming for the last time, the function will finish, handing control of the thread back to your function, along with a value or an error.
- Let’s take another look at
AsynchronousFunctionOne
to see what can happen when it suspends. - When AsynchronousFunctionOne calls URLSession’s async data method, the data method stops executing on the thread in the special way that only async functions can: by suspending. It gives control of the thread to the system and asks the system to schedule the work for URLSession’s data method. Here thread is main so it will suspend main thread , Note most important that’s why I am iterating again and again suspend means main thread is free , firther more URLSession do network connection and everything in another thread internal to its implementation but why main thread is suspend since it is passed
- On suspension point, the system is in control, and that work may not be started immediately. The thread can be used for other things instead. Let’s see how that might happen. Suppose that after
AsynchronousFunctionOne
has been called, the user taps a button which will upload some data. Say, for example, that they react to a post. Then the system is free to execute the work to post the user’s reaction before the previously queued-up work. - Once that late-breaking work has completed, URLSession’s data method may be resumed. Or the system might execute other work instead. Finally, once the data method finishes, it will return to
AsynchronousFunctionOne
. The fact that other work can be performed while a function is suspended is why Swift insists that you mark async calls with the await keyword. You need to be aware that the state of your app can change dramatically when your function suspends. we will cover details this point later why it is important - Now, this is also true when you use completion handlers. But because you don’t have all the ceremony and indentation they entail in async/await code, the await keyword is how you notice that a block of code doesn’t execute as one transaction. The function may suspend, and other things may happen while it’s suspended between the lines of the function.
- More than that, the function may resume onto an entirely different thread.
In short
- when you mark a function async, you’re allowing it to suspend. And when a function suspends itself, it suspends its callers too. So its callers must be async as well. If not then we use Task as we did in viewDidLoad
- To point out where in an async function it might suspend one or many times, the await keyword is used. (Called Suspension Point), Note it is possible that function may or may not suspend , also when. resume comes in a different thread
- while an async function is suspended, the thread is not blocked. So the system is free to schedule other work. Even work that gets kicked off later can be executed first. That means your app’s state can change a great deal while the function is suspended. Finally, when an async function resumes, the results returned from the async function it called flow back into the original function, and execution continues right where it left off. You’ve seen how async/await works in Swift.
Use async/await with URLSession
Networking is inherently asynchronous, and in iOS 15 and macOS Monterey apple introduced a set of new APIs in URLSession for you to take advantage of Swift concurrency features as we see in previous sections as well
In Figure 9 there is a code to download image from url . It’s using the completionHandler-based convenience method on URLSession. The code seems straightforward, and it worked in limited testing. However, it has at least three mistakes.
- Let’s dive in. First, let’s follow the control flow. We create a data task and resume it. Then once the task is done, we jump into the completion handler, we check the response, create an image, and that’s where the control flow ends., we are jumping back and forth.
- What about threading? It is surprisingly complex for this small piece of code. We have three different execution contexts in total. The outmost layer runs on whatever thread or queue of the caller, URLSessionTask completionHandler runs on session’s delegate queue, and the final completion handler runs on the main queue. Since the compiler cannot help us here, we have to use extreme caution to avoid any threading issues such as data races. Also noticed something wrong. The calls to the completionHandler are not consistently dispatched to the main queue. This could be a bug.
- we are missing an early return here. The completionHandler can be called twice if we got an error. This could violate assumptions made by the caller. Finally, this might not be very obvious, but UIImage creation can fail. If the data is in an incorrect format, this UIImage initializer returns nil, so we would have called the completionHandler with both nil image and nil error. This is likely not expected. In Pros of Async/Await we write a point safe and you will see once we convert it into async-await code will be safer too
Now this is the new version using async/await. Wow, it’s so much simpler!
- The control flow is linear from top to bottom,
- we know that everything in this function runs in the same concurrency context, so we no longer need to worry about threading issues. Here, we used the new async data method on URLSession. It suspends the current execution context without blocking, and it returns data and response on successful completion, or throws an error. We also used the throw keyword to throw errors when the response is unexpected. This allows the caller to catch and handle the error using Swift native error handling.
- , the compiler would bark if we try to return an optional UIImage from this function, so it essentially forces us to handle nil correctly.
Here are the signatures of the methods we just used to fetch data from the network. URLSession.data methods accept either a URL or a URLRequest. They are equivalent to existing data task convenience methods. Please refer to this to see video of this WWDC talk