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

SwiftUI异步请求返回前View被释放,如何保证一致性?

解决SwiftUI导航中ViewModel重复初始化与生命周期问题

核心问题分析

SwiftUI的navigationDestination闭包会因预渲染、状态变化等原因被多次调用,导致FormView及其内部的FormViewModel被重复初始化。当网络请求返回时,旧的FormViewModel已被释放,无法更新UI数据。

解决方案

1. 使用@StateObject管理ViewModel生命周期

将UserFormView中的@ObservedObject替换为@StateObject,@StateObject会确保ViewModel仅初始化一次,且在View的整个生命周期内保持实例稳定,即使View被SwiftUI重建也不会销毁ViewModel。

修改后的UserFormView代码:

struct UserFormView: View {
    // 用@StateObject替代@ObservedObject,保证ViewModel单例且生命周期与View绑定
    @StateObject private var viewModel = FormViewModel()
    
    @State var first: String = ""
    @State var last: String = ""
    @State var department: Department? = nil
    
    var id: Int?
    
    var body: some View {
        VStack {
            HStack {
                TextField("First", text: $first)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .font(.system(size: 16))
                               
                TextField("Last", text: $last)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .font(.system(size: 16))
            }

            HStack {
                Picker(selection: $department, label: Text("")) {
                    ForEach(viewModel.departments, id: \.self) { department in
                        Text(department.name ?? "").tag(department)
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .padding()
            }
        }
        .padding()
        .navigationTitle("User Form")
        .onAppear {
            viewModel.findAllDepartments()
        }
    }
}

2. 避免重复发起网络请求

在FormViewModel的findAllDepartments方法中添加判断,仅当部门列表为空时才发起请求,防止多次调用onAppear导致重复请求。

修改后的FormViewModel代码:

class FormViewModel: ObservableObject {
    
    @Published var user: User? = nil
    @Published var departments: [Department] = []
    
    private var cancellable = Set<AnyCancellable>()
    private var rand = Int(arc4random_uniform(10)) + 1
    
    init() {
        print("init \(rand)")
    }

    func findAllDepartments() {
        // 已加载过部门列表则直接返回,避免重复请求
        guard departments.isEmpty else { return }
        
        delegate?.findAllDepartments()
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: {[weak self] completion in
                switch completion{
                case .finished:
                    print("finished")
                case .failure(let error):
                    print(error)
                }
            }, receiveValue: { [weak self] departments in
                self?.departments.append(contentsOf: departments)
                // 无需手动发送objectWillChange,@Published会自动触发
                // self?.objectWillChange.send()
            })
            .store(in: &cancellable)
    }

    deinit {
        print("deinit \(rand)")
    }
}

3. 可选:将ViewModel提升至父View管理(多实例场景)

如果需要为不同用户ID维护独立的ViewModel实例,可以在UserListView中用字典存储ViewModel,通过参数传递给FormView,确保每个ID对应唯一的ViewModel:

修改后的UserListView代码示例:

struct UserListView: View {
    @ObservedObject private var viewModel = ViewModel()
    // 为每个用户ID存储对应的FormViewModel
    @StateObject private var formViewModels = [Int: FormViewModel]()

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.users) { user in
                    NavigationLink(value: user.id) {
                        Text(user.givenName)
                    }
                }
            }
            .navigationTitle("UserList")
            .navigationDestination(for: Int.self, destination: { id in
                // 懒加载或创建对应ID的ViewModel
                let vm = formViewModels[id] ?? FormViewModel()
                formViewModels[id] = vm
                UserFormView(id: id, viewModel: vm)
            })
            .onAppear {
                viewModel.findAll()
            }
        }
    }
}

// 修改UserFormView接收外部ViewModel
struct UserFormView: View {
    @ObservedObject var viewModel: FormViewModel
    @State var first: String = ""
    @State var last: String = ""
    @State var department: Department? = nil
    
    var id: Int?
    
    // ... 其余body代码不变
}

关键说明

  • @StateObject是SwiftUI 2.0+引入的属性包装器,专门用于管理ObservableObject的生命周期,确保View重建时ViewModel不被销毁。
  • 手动判断请求状态避免重复网络调用,减少不必要的资源消耗和数据不一致问题。
  • 当需要为多个实例维护独立ViewModel时,父View集中管理实例是更可靠的方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 00:36:14