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

SwiftUI异步获取数据时ObservedObject失效,StateObject正常?

使用StateObject视图能重渲染,ObservedObject却不行的原因分析

问题场景

我有两个SwiftUI视图:根应用视图MyApp和使用ObservedObject视图模型的SecondView。从根视图创建带Firebase对象ID的SecondViewModel并传递给SecondView,希望SecondView出现时向Firebase请求数据,期间显示加载界面。

使用ObservedObject时,SecondView在获取数据后从未重渲染;换成StateObject则解决了问题,但不清楚原因。我认为根视图不会重渲染并重建ViewModel,而且登录视图登录后强制RootView重渲染,但SecondView不应被多次渲染。

相关代码

MyApp代码

@main
struct MyApp: App {
    @ObservedObject private var userService = UserService()
    
    init() {
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        WindowGroup {
            NavigationView {
                Group {
                    if (userService.currentUser != nil) {
                        SecondView(secondViewModel: SecondViewModel(somethingId: "someIdGoesHere"))
                    } else {
                        LoginView()
                    }
                }
                .environmentObject(userService)
            }
        }
    }
}

SecondViewModel代码

class SecondViewModel: ObservableObject {
    @Published private(set) var thing: Thing? = nil
    private let thingService = ThingService()
    private let thingId: String
    
    init(thingId: String) {
        self.thingId = thingId
    }
    
    func load() async {
        self.thing = await thingService.GetThing(thingId: thingId) // Async call to firebase
    }
}

SecondView代码

struct SecondView: View {
 
    // Task runs to completion but 'Loading...' never goes away.
    @ObservedObject var secondViewModel: SecondViewModel
    
    var body: some View {
        VStack(spacing: 5) {
            if let thingText = secondViewModel.thing {
                Text(thingText)
            } else {
                Text("Loading...")
            }
        }
        .onAppear {
            Task {
                await secondViewModel.load()
            }
        }
    }
}

核心原因

问题出在ViewModel的生命周期管理和视图body重计算的时机:

  1. MyApp的body会重复生成新的ViewModel实例
    当登录成功后,userService.currentUser更新(触发@Published通知),MyApp的body会重新计算。此时代码里每次都会创建一个全新的SecondViewModel实例传给SecondView。旧的ViewModel虽然完成了数据加载,但已经被丢弃,新的ViewModel还没执行load方法,所以thing始终为nil,显示"Loading..."。

  2. @ObservedObject和@StateObject的本质差异

    • @ObservedObject:视图不持有对象的所有权,完全依赖外部传入的实例。如果外部传入新实例,视图会立即绑定新对象,旧对象的状态变化不会再触发视图更新。
    • @StateObject:视图拥有对象的所有权,SwiftUI会确保在视图的整个生命周期内只创建一次该对象,无论视图body重算多少次,实例始终保持不变。当你把SecondView里的@ObservedObject改成@StateObject时,ViewModel实例稳定,数据加载后@Published的thing变化就能正确触发视图重渲染。

修复方案

方案一:让SecondView自己持有ViewModel(推荐)

把SecondViewModel的初始化移到SecondView内部,用@StateObject持有:

struct SecondView: View {
    // 视图自己持有ViewModel,生命周期和视图绑定
    @StateObject private var secondViewModel = SecondViewModel(thingId: "someIdGoesHere")
    
    var body: some View {
        VStack(spacing: 5) {
            if let thingText = secondViewModel.thing {
                Text(thingText)
            } else {
                Text("Loading...")
            }
        }
        .onAppear {
            Task {
                await secondViewModel.load()
            }
        }
    }
}

此时MyApp里只需直接调用SecondView()即可。

方案二:在根视图保持ViewModel实例稳定

如果必须在外部传入ViewModel,在MyApp里用@StateObject持有SecondViewModel,确保每次传递的是同一个实例:

@main
struct MyApp: App {
    @ObservedObject private var userService = UserService()
    // 用@StateObject确保ViewModel实例稳定
    @StateObject private var secondViewModel = SecondViewModel(thingId: "someIdGoesHere")
    
    init() {
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        WindowGroup {
            NavigationView {
                Group {
                    if (userService.currentUser != nil) {
                        // 传递同一个实例
                        SecondView(secondViewModel: secondViewModel)
                    } else {
                        LoginView()
                    }
                }
                .environmentObject(userService)
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:46:06