Let's suppose we have some asynchronous code. At some point we must wrap it in a Task {…}
in order to run it from a synchronous context.
So where is the canonical way to do so? ViewModel
or ViewController
?
If we wrap it with Task {…}
in ViewModel
, the ViewModel
functions become effectively synchronous, and calling them from ViewController
will still require all those completions/delegates/closures/RXs dance to accomplish some UI updates after asynchronous work finishes.
In the other hand, if we mark ViewModel
functions as async
and call them from ViewController
within Task {…}
body, it seems to solve the problem. So is it a way to go?
I would not re-introduce legacy completion patterns (closures, delegates, etc.). That defeats the purpose of Swift concurrency, to gracefully manage asynchronous dependencies. E.g., in WWDC 2021 video Swift concurrency: Update a sample app, they show example(s) of how we eliminate completion handlers with Swift concurrency.
So, designate the asynchronous view model methods as async
. Then, the view controller will use Task {…}
to enter the asynchronous context of Swift concurrency so that it can await
the async
method of the view model and then trigger the UI update when it is done.
But, use of Task {…}
is not limited to the view controller. The view model may use it, too (e.g., where one needs to save a task in order to possibly cancel it later for some reason).
But you ask:
if we mark
ViewModel
functions asasync
and call them fromViewController
withinTask {…}
body, it seems to solve the problem. So is it a way to go?
Precisely. If the method is really doing something asynchronous, then mark it as async
and the view controller will invoke it from within a Task {…}
.
All of that having been said, in SwiftUI projects, where the view model often communicates updates to the view with ObservableObject
and @Published
properties, I would remain within that intuitive and natural pattern. At that point, where you choose to cross into the asynchronous context becomes a little less compelling/critical.
That having been said, from a testability perspective, though, you still want to be able to know when the view model’s asynchronous task is done, so I would probably still make the view model’s methods async
.