You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI视图消失时Task未自动取消?原因及正确实现方案

问题:SwiftUI视图消失时内部Task未被自动取消的原因及解决方案

根据Apple官方文档,task{}会在视图消失时自动取消。
SwiftUI会在视图消失后的某个时间点、操作完成前自动取消任务。

但在以下场景中,ChildView消失(返回至ParentView)时,loadData方法中的Task为何未被取消?

struct ChildView: View {
    
    @State private var data: [Double] = []
    
    private func loadData() async {
        // 为什么ChildView消失时这个Task不会被自动取消?
        // 它仍会在后台执行
        Task(priority: .background) {
            // 模拟大量数据处理
            self.data = (1...3_000_000).map { _ in Double.random(in: -10...30) }
            print("Task ended")
        }
    }
    
    var body: some View {
        Text("This is a child view")
            .task { await loadData() }
    }
}

struct ParentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink(destination: { ChildView() }) {
                Text("Show child view")
            }
        }
    }
}

即使ChildView消失,loadData中的Task仍会继续执行;连续多次初始化ChildView时,该Task还会重复执行。这种情况可能引发内存泄漏,尤其是当使用带有@Published属性的类替代ChildView中的@State属性时。请问此类场景下使用Task()的正确实现方式是什么?


原因分析

  • SwiftUI的.task修饰符创建的任务会绑定到视图生命周期,视图消失时该任务会被取消,但这个取消信号只会传递给该任务本身以及通过结构化并发创建的子任务。
  • 你在loadData里手动创建的Task(priority: .background)属于非结构化任务,它不受外部.task任务的取消影响,会脱离视图生命周期独立运行,因此视图消失后依然会执行完毕。

正确实现方式

方式一:使用结构化并发,无需手动创建新Task

直接在loadData的异步函数中执行操作,不需要额外包裹Task,这样操作会在.task创建的任务上下文中运行,视图消失时会被自动取消:

struct ChildView: View {
    
    @State private var data: [Double] = []
    
    private func loadData() async {
        // 直接执行操作,无需额外创建Task
        self.data = (1...3_000_000).map { _ in Double.random(in: -10...30) }
        print("Task ended")
    }
    
    var body: some View {
        Text("This is a child view")
            .task { await loadData() }
    }
}

方式二:绑定外部任务的取消令牌到手动创建的Task

如果确实需要手动创建非结构化任务(比如调整优先级),可以将外部任务的取消令牌传递进去,让新任务响应外部的取消信号:

struct ChildView: View {
    
    @State private var data: [Double] = []
    
    private func loadData() async {
        // 获取当前任务的取消令牌
        let cancellationToken = Task.currentPriority
        // 创建新任务时传入令牌,绑定到当前上下文
        Task(priority: .background, cancellationToken: cancellationToken) {
            let result = (1...3_000_000).map { _ in
                // 定期检查任务是否已取消,若取消则提前退出
                if Task.isCancelled { return 0 }
                return Double.random(in: -10...30)
            }
            // 仅在任务未取消时更新数据
            if !Task.isCancelled {
                self.data = result
                print("Task ended")
            }
        }
    }
    
    var body: some View {
        Text("This is a child view")
            .task { await loadData() }
    }
}

方式三:手动管理任务生命周期

通过@State存储任务实例,在视图消失时手动取消任务:

struct ChildView: View {
    
    @State private var data: [Double] = []
    @State private var processingTask: Task<Void, Never>?
    
    private func loadData() {
        processingTask = Task(priority: .background) {
            let result = (1...3_000_000).map { _ in
                if Task.isCancelled { return 0 }
                return Double.random(in: -10...30)
            }
            if !Task.isCancelled {
                self.data = result
                print("Task ended")
            }
        }
    }
    
    var body: some View {
        Text("This is a child view")
            .onAppear { loadData() }
            .onDisappear { processingTask?.cancel() } // 视图消失时手动取消任务
    }
}

内容的提问来源于stack exchange,提问作者Hollycene

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 19:52:38