I'm pretty sure I'm missing something completely obvious, but I can't see what. I watched WWDC session, read the Swift evolution blog, and they all make sense, but still it doesn't click for me. Please help me out here :) .
I'm diving into adopting the 'new' async/await style of coding (I know, it's old news at this point, but I could only get to it now), and so I'm all pumped to get my code to go eleven and therefore I wrote a small data-downloader class. It has one method, well two: one oldskool function with a completionHandler, and one new style async/await one.
When using the oldskool one, it works as everyone would expect:
print(1)
dataFetcher.fetchSomeData
{
print(2)
let data = $0
// process data ...
print(3)
}
print(4)
The output is, unsurprisingly:
1
4
2
3
Now, when I use my new style function:
let data = await dataFetcher.fetchSomeData()
// process data ...
Xcode gives me an error:
'async' call in a function that does not support concurrency
That makes sense, I am calling this in the viewDidLoad()
method of a UIViewController
subclass. Can't mark viewDidLoad()
as await, as super's implementation is not async. No problem, let me wrap it in Task
:
print(1)
Task
{
print(2)
let data = await dataFetcher.fetchSomeData()
// process data ...
print(3)
}
print(4)
No errors, and this code works exactly as expected, the output is:
1
4
2
3
So now I am wondering: why take the effort of changing/adding code for async/await style function that ultimately end up requiring exactly the same amount of code, and is exactly as non-linear as completionHandlers?
Note that the dataFetcher
only has one property, an instance ofURLSession
, so I am also not even managing my own queues or threads in the oldskool method vs the new one. They just wrap URLSession
's functions:
func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
and
func download(for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (URL, URLResponse)
Is async/await useful only with SwiftUI maybe? What am I missing here? Please help me see the light.
I think you have identified the benefits, but let me restate them in slightly different words:
-
When you use completion handlers, especially for multiple successive completions, you end up with indented code getting deeper and deeper nesting in braces. This is familiarly known as the "pyramid of doom". Using async/await clarifies that your code is actually sequential, which is a big win for code readability.
-
Using
Task { … }
to transition from a sync to an async context makes it clear thatviewDidLoad
returns without waiting for the task to run. You already know this — which is why you expect the order 1, 4, 2, 3 — but it's a mental leap that developers new to Swift have to make.
It's also worth adding that adopting async/await also opens up the world of Swift actors, which are the language's way of helping you avoid data races and other concurrency problems.