SwiftUI中带关联值的State枚举内存占用异常问题咨询
问题:SwiftUI视图状态管理中的内存持续上升问题
为管理SwiftUI视图的视觉状态,我实现了一个包含State枚举的ObservedObject,通过带关联值的枚举展示远程数据获取状态:
final class ViewModel: ObservableObject { @Published private(set) var state: State = .idle func doWork() { // 切换到加载状态,显示加载器 state = .loading doSomeRemoteWork { [weak self] values in // 任务完成,展示结果 self?.state = .loaded(values) } } enum State { case idle case loading case loaded([Value]) case error(any Error) } } // 仅包含基础数据的结构体 struct Value: Identifiable { let id: String let someProperty: String let anotherProperty: Double }
对应的SwiftUI视图:
struct ContentView: some View { @ObservedObject private var vm: ViewModel init(vm: ViewModel) { self.vm = vm } var body: some View { switch vm.state { case .idle: Button("Get values") { vm.doWork() } case .loading: Text("loading...") case .loaded(let values): Text("got \(values.count) values") .onTapGesture { refresh() } case .error(let error): Text("got an error : \(error.localizedDescription)") } } private func refresh() { vm.doWork() } }
该实现可正常运行,但调用doWork()刷新时,应用内存占用持续上升且从未回落。按我的理解,枚举会强持有关联值,但将state设置为.loading时,应该会清除之前的枚举关联值,请问问题出在哪?如何解决?
分析与解决方案
问题根源:未取消的异步任务
你对枚举关联值的理解是对的——当state从.loaded(values)切换到.loading时,原values数组因为没有其他强引用,会被ARC自动回收。内存持续上升的核心原因是每次调用doWork()都会发起新的异步请求,但旧的未完成请求没有被取消:这些旧请求会一直占用内存直到任务结束,返回的数据也会被闭包临时持有,导致内存堆积。
解决方案:添加任务取消机制
在ViewModel中新增任务管理逻辑,每次发起新请求前先取消未完成的旧任务,避免无用任务占用内存。以下是两种常见场景的修改示例:
1. 使用Combine处理异步请求
如果你的doSomeRemoteWork基于Combine实现,可通过AnyCancellable管理任务:
final class ViewModel: ObservableObject { @Published private(set) var state: State = .idle private var cancellable: AnyCancellable? // 用于管理异步任务 func doWork() { cancellable?.cancel() // 取消之前的未完成任务 state = .loading // 假设doSomeRemoteWork返回一个Publisher doSomeRemoteWork() .receive(on: DispatchQueue.main) .sink(receiveCompletion: { [weak self] completion in guard let self = self else { return } if case .failure(let error) = completion { self.state = .error(error) } }, receiveValue: { [weak self] values in self?.state = .loaded(values) }) .store(in: &cancellable) } enum State { case idle case loading case loaded([Value]) case error(any Error) } }
2. 使用URLSessionDataTask处理网络请求
如果是基于原生URLSession的网络请求,可通过保存当前任务实例来取消:
final class ViewModel: ObservableObject { @Published private(set) var state: State = .idle private var currentTask: URLSessionDataTask? // 保存当前网络任务 func doWork() { currentTask?.cancel() // 取消旧任务 currentTask = nil state = .loading // 示例网络请求 guard let url = URL(string: "your_api_url") else { return } currentTask = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let self = self else { return } DispatchQueue.main.async { if let error = error { self.state = .error(error) return } // 解析数据为Value数组(示例逻辑) let values = try? JSONDecoder().decode([Value].self, from: data ?? Data()) ?? [] self.state = .loaded(values) } } currentTask?.resume() } enum State { case idle case loading case loaded([Value]) case error(any Error) } }
额外排查建议
- 用Xcode的内存图工具或Leaks工具定位内存堆积的对象,确认是否有其他引用类型被意外持有;
- 检查
Value结构体是否间接持有大体积引用类型(比如嵌套的类实例),如果有需优化引用逻辑。
内容的提问来源于stack exchange,提问作者tom
相关产品推荐
相关产品推荐

