使用触发机制将异步任务附加到 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)
}
}
trigger
并初始化 Int
类型的 TaskTrigger
。trigger
上调用 trigger(value:id:)
方法,并传入我们的值。task
视图修饰符的新变体附加到我们的视图,并将其绑定到我们的 trigger
。trigger
被触发时执行。我们之前传递到 trigger(value:id:)
方法中的值作为参数传递到闭包中。注意 您可能想知道为什么
trigger(value:id:)
方法上有一个可选参数id
。默认情况下,每当调用该方法时,它都会创建一个新的UUID
。这意味着我们可以多次点击同一个按钮,如果之前的操作仍在运行,则会被取消。如果您不希望这种情况发生,请显式设置
id
参数,它将不会取消之前的操作,因为value
和id
仍然相同。
对于不需要附加值的触发器,我们可以简单地使用 PlainTaskTrigger
(它是 TaskTrigger<Bool>
的类型别名)
@State var trigger = PlainTaskTrigger()
var body: some View {
Button("Do Something") {
trigger.trigger()
}
.task($trigger) {
await someAsyncOperation()
}
}
为了在使用带有按钮的 TaskTrigger
时更易于使用,我们还可以使用 TaskTriggerButton
。以下示例与之前的示例等效
TaskTriggerButton("Do Something") {
await someAsyncOperation()
}
如果您有任何关于如何进一步推进的想法,我很乐意在 issue 中讨论。