TaskTrigger

使用触发机制将异步任务附加到 SwiftUI 视图。有关示例,请参阅此处

目录

用法

dependencies: [
    .package(url: "https://github.com/lukepistrol/TaskTrigger.git", from: "0.1.0"),
],

请务必在任何您想使用它的地方 import TaskTrigger

文档

文档在此处可用

您也可以查看示例项目

概述

在 SwiftUI 中使用 Swift 的结构化并发时,最佳实践是将任务绑定到相关视图的生命周期,以便在视图被关闭时支持任务取消。

注意 通常任务可能不会花费很长时间,以至于我们甚至关心取消。但想象一下从远程服务器下载大量数据,这取决于网络连接,可能需要相当长的时间。当用户决定关闭视图时,最好也取消任务,以避免继续执行不再必要的工作。

当然,这并不保证子任务会立即停止,而是它们会通过 Task.isCancelled 被告知已被取消。它们是否以及如何处理取消完全取决于子任务的实现。

问题

这已经可以通过使用 task(id:priority:_:) 视图修饰符来实现。但是,这需要为 id 进行额外的管理。

在一个简单的示例中,我们只想触发一些任务,我们可以使用 Bool 作为 id

@State var triggerTask: Bool = false

var body: some View {
    Button("Do Something") {
        triggerTask = true
    }
    .task(id: triggerTask) {
        guard triggerTask else { return }
        await someAsyncOperation()
        triggerTask = false
    }
}

注意 我们需要检查 triggerTask 是否确实为 true,否则返回。我们还需要在执行结束时重置 triggerTask。否则,再次点击将不会再次触发 task(id:priority:_:)

第一步至关重要,因为当我们在最后重置 triggerTask 时,我们会从自身内部再次触发任务。

在更复杂的示例中,这更有意义,在更复杂的示例中,我们有一个视图列表,我们想根据某些标识符执行某些操作

@State var triggerTaskId: Int?

var body: some View {
    List(0..<10) { index in
        Button("Item \(index)") {
            triggerTaskId = index
        }
    }
    .task(id: triggerTaskId) {
        guard let triggerTaskId else { return }
        await someAsyncOperation(for: triggerTaskId)
        triggerTaskId = nil
    }
}

注意 在这种情况下,我们有一个可选整数,并在按下列表中的按钮后将其设置为按钮的索引。我们需要解包可选值,然后将其重置为 nil

这种方法虽然效果很好,但在我们的视图中直接带来了很多开销。此外,我们必须直接从我们的任务中调用状态值,当我们将状态保存在视图模型中时,这可能会很麻烦。

解决方案

为了简化调用方的工作,让我们将所有这些功能包装在一个简单的类型 TaskTrigger 中。

@State var trigger = TaskTrigger<Int>()

var body: some View {
    List(0..<10) { index in
        Button("Item \(index)") {
            trigger.trigger(value: index)
        }
    }
    .task($trigger) { index in
        await someAsyncOperation(for: index)
    }
}
  1. 我们声明一个新的状态变量 trigger 并初始化 Int 类型的 TaskTrigger
  2. 在我们的按钮中,我们在我们的 trigger 上调用 trigger(value:id:) 方法,并传入我们的值。
  3. 我们将 task 视图修饰符的新变体附加到我们的视图,并将其绑定到我们的 trigger
  4. 主体仅在 trigger 被触发时执行。我们之前传递到 trigger(value:id:) 方法中的值作为参数传递到闭包中。
  5. 所有与取消相关的处理、健全性检查以及重置状态都在幕后自动处理。

注意 您可能想知道为什么 trigger(value:id:) 方法上有一个可选参数 id。默认情况下,每当调用该方法时,它都会创建一个新的 UUID。这意味着我们可以多次点击同一个按钮,如果之前的操作仍在运行,则会被取消。

如果您不希望这种情况发生,请显式设置 id 参数,它将不会取消之前的操作,因为 valueid 仍然相同。

对于不需要附加值的触发器,我们可以简单地使用 PlainTaskTrigger(它是 TaskTrigger<Bool> 的类型别名)

@State var trigger = PlainTaskTrigger()

var body: some View {
    Button("Do Something") {
        trigger.trigger()
    }
    .task($trigger) {
        await someAsyncOperation()
    }
}

TaskTriggerButton

为了在使用带有按钮的 TaskTrigger 时更易于使用,我们还可以使用 TaskTriggerButton。以下示例与之前的示例等效

TaskTriggerButton("Do Something") {
    await someAsyncOperation()
}

贡献

如果您有任何关于如何进一步推进的想法,我很乐意在 issue 中讨论。


Buy Me A Coffee