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 useTask {…}
to enter the asynchronous context of Swift concurrency so that it canawait
theasync
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:
Precisely. If the method is really doing something asynchronous, then mark it as
async
and the view controller will invoke it from within aTask {…}
.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
.