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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 12:27:51