在ViewModel还是ViewController中使用”Task {}”?

huangapple go评论71阅读模式
英文:

Where should we use "Task {}": in ViewModel or ViewController?

问题

让我们假设我们有一些异步代码。在某个时候,我们必须在 Task {…} 中包装它,以便从同步上下文中运行它。
那么,在哪里是这样做的规范方式?是在 ViewModel 中还是在 ViewController 中?

如果我们在 ViewModel 中用 Task {…} 包装它,那么 ViewModel 函数将变成有效的同步函数,从 ViewController 调用它们仍然需要在异步工作完成后执行某些 UI 更新的完成/代理/闭包/RXs 舞蹈。

另一方面,如果我们将 ViewModel 函数标记为 async 并在 ViewController 中的 Task {…} 主体内调用它们,似乎可以解决这个问题。所以这是一个可行的方法吗?

英文:

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?

答案1

得分: 1

我不会重新引入传统的完成模式(闭包、代理等)。这违背了Swift并发的目的,即优雅地管理异步依赖关系。例如,在WWDC 2021视频Swift并发:更新示例应用中,他们展示了如何使用Swift并发消除完成处理程序的示例。

因此,请将异步视图模型方法标记为async。然后,视图控制器将使用Task {…}进入Swift并发的异步上下文,以便可以await视图模型的async方法,然后在完成时触发UI更新。

但是,Task {…}的使用不仅限于视图控制器。视图模型也可以使用它(例如,在需要保存任务以便以后可能取消的情况下)。

但是你可能会问:

>如果我们将ViewModel函数标记为async,并在ViewController中的Task {…}体内调用它们,似乎可以解决问题。那么这是一个可行的方法吗?

确切如此。如果该方法确实在执行某些异步操作,那么将其标记为async,视图控制器将从Task {…}内部调用它。


尽管如此,在SwiftUI项目中,视图模型通常使用ObservableObject@Published属性与视图进行通信,我仍然会保持在这种直观和自然的模式内。在那个点上,选择跨入异步上下文变得不那么引人注目/关键。

尽管如此,从可测试性的角度来看,您仍然希望能够知道视图模型的异步任务何时完成,因此我可能仍然会将视图模型的方法标记为async

英文:

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 as async and call them from ViewController within Task {…} 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.

huangapple
  • 本文由 发表于 2023年6月19日 18:59:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76505985.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定