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

SwiftUI中如何实现类在每个App会话周期内仅初始化一次

问题说明

需求为实现指定逻辑在单个App会话周期内仅执行一次:以HomeViewModel为例,首次启动应用时执行fetchSongs()拉取Firebase数据,在应用被强制关闭、或手动触发重置前,不重复执行拉取逻辑。
当前实现的问题:每次HomeView显示时都会重新初始化ViewModel,导致重复拉取、重载数据。
原有问题代码:

class HomeViewModel: ObservableObject {
    @Published var songs = [Song]()
    
    init() {
        fetchSongs()
    }
}
// 原代码存在语法错误:结构体定义不需要多余括号,正确写法为HomeView: View而非HomeView(): View
struct HomeView: View {
    @ObservedObject private var viewModel = HomeViewModel()
    
    var body: some View {
        VStack {
            ForEach(viewModel.songs) { song in
                // 原代码此处存在类型错误:Song类型无法直接传入Text,需传入字符串属性,例如song.name
                Text(song.name)
            }
        }
    }
}
问题根源

SwiftUI的View是值类型,每次视图重新渲染、导航栈切换、Tab切换等场景下,视图实例可能被重复创建。在HomeView内部直接初始化HomeViewModel,会导致每次视图重建时都生成新的ViewModel实例,自动调用init()里的fetchSongs()。

实现方案

根据场景有两种常用实现,优先选择第一种,架构合理性更高。

方案1:在应用根层级持有共享ViewModel(推荐)

将ViewModel的持有逻辑放到App启动入口或根视图,保证整个App会话周期内ViewModel只被初始化一次,通过环境注入或参数传递给子视图使用。

  1. 在App入口处初始化ViewModel,用@StateObject托管生命周期,注入到全局环境:
@main
struct YourAppName: App {
    // 此处初始化的ViewModel会在整个App生命周期内仅创建一次
    @StateObject private var homeViewModel = HomeViewModel()
    
    var body: some Scene {
        WindowGroup {
            HomeView()
                .environmentObject(homeViewModel)
        }
    }
}
  1. 修改HomeView,从环境中获取共享的ViewModel实例,不再自行初始化:
struct HomeView: View {
    @EnvironmentObject private var viewModel: HomeViewModel
    
    var body: some View {
        VStack {
            ForEach(viewModel.songs) { song in
                Text(song.name)
            }
            // 如需手动触发重新拉取,直接调用ViewModel的公开方法即可
            Button("刷新数据") {
                viewModel.fetchSongs()
            }
        }
    }
}

如果需要支持手动重置初始化逻辑,只需要在HomeViewModel中新增对应的公开方法即可。

方案2:单例模式(适合小型项目快速实现)

给ViewModel添加静态单例实例,保证全局访问到的都是同一个实例,私有化初始化方法避免外部重复创建。

class HomeViewModel: ObservableObject {
    static let shared = HomeViewModel()
    @Published var songs = [Song]()
    
    private init() {
        fetchSongs()
    }
    
    func fetchSongs() {
        // 原有拉取逻辑
    }
    
    // 手动重置方法
    func reset() {
        songs.removeAll()
        fetchSongs()
    }
}

使用时直接获取单例即可,不会重复创建实例:

struct HomeView: View {
    @ObservedObject private var viewModel = HomeViewModel.shared
    
    var body: some View {
        // 页面内容同方案1
    }
}

注意:单例模式存在依赖隐藏、单元测试难以替换Mock实例的问题,中大型项目优先选择方案1的依赖注入实现。

补充说明

如果ViewModel确实是单个页面独立使用、不需要跨页面共享,只需要把视图内部的@ObservedObject替换为@StateObject即可:@StateObject由SwiftUI托管生命周期,会在视图首次创建时初始化实例,后续视图重复渲染时会复用已有实例,不会重复触发初始化逻辑。但这种方式无法保证跨页面、跨Tab切换时实例不被销毁,仅适合页面内部私有状态场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 01:36:47