如何在ViewModel中取消长期Task?解决内存泄漏问题
修复SwiftUI ViewModel内存泄漏与Task取消问题
你的推测完全正确——执行observeNotification的Task持有了TestViewModel的强引用,而NotificationCenter的异步序列会持续监听方向变化通知,导致Task无法自动结束,ViewModel也就无法被销毁,进而出现检测逻辑持续运行的问题。
步骤1:在ViewModel中管理Task引用并实现取消逻辑
给TestViewModel添加Task属性保存异步任务引用,同时提供取消方法,主动终止任务以释放引用:
@MainActor class TestViewModel: ObservableObject { @Published var isPortrait = false private var orientationTask: Task<Void, Never>? // 保存Task引用 init() { print("TestViewModel init.") setup() } deinit { print("TestViewModel deinit.") } func setup() { orientationTask = Task { await observeNotification() } } // 新增:主动取消任务的方法 func cancelObservation() { orientationTask?.cancel() orientationTask = nil } private func observeNotification() async { let sequence = NotificationCenter.default.notifications(named: UIDevice.orientationDidChangeNotification) .map { _ in await UIDevice.current.orientation } do { for try await value in sequence { // 用try await捕获取消信号 print("orientationDidChangeNotification changed, orientation = \(value).") isPortrait = value == .portrait } } catch is CancellationError { print("Orientation observation task cancelled.") } catch { // 处理其他潜在错误 } } }
步骤2:在View消失时触发Task取消
在TestView的onDisappear回调中调用ViewModel的取消方法,确保Sheet关闭时主动终止异步任务:
struct TestView: View { @StateObject private var vm = TestViewModel() @Environment(\.dismiss) var dismiss var body: some View { VStack { Text("isPortrait = \(vm.isPortrait.description)") Button("Dismiss") { dismiss() } } .onDisappear { print("TestView onDisappear.") vm.cancelObservation() // 触发任务取消 } } }
效果验证
修改后关闭TestView时,会依次打印:
TestView onDisappear.Orientation observation task cancelled.TestViewModel deinit.
方向检测逻辑会停止运行,ViewModel成功释放,内存泄漏问题解决。
关键原理
Task.cancel()会触发CancellationError,异步序列的for try await循环捕获错误后终止,Task不再持有ViewModel的强引用。- 主动取消Task后,
NotificationCenter的异步监听会自动停止,避免资源浪费。
内容的提问来源于stack exchange,提问作者foolbear
相关产品推荐
相关产品推荐

