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

SwiftUI中使用ViewModel如何避免不必要的内存分配开销?

解决SwiftUI中NavigationLink提前初始化目标视图与ViewModel的问题

这个问题确实戳中了SwiftUI导航里一个很容易踩的坑——默认写法下,NavigationLink会在父视图计算body时就提前初始化所有目标视图和对应的ViewModel,哪怕用户根本没点进去,对于初始化成本高的ViewModel来说,这完全是不必要的性能浪费。我来给你几个实用的解决办法:

方案1:iOS 16+ 推荐使用 .navigationDestination(最优解)

从iOS16开始,SwiftUI提供了navigationDestination修饰符,它是真正的懒加载实现——只有当用户触发导航动作时,才会创建对应的目标视图和ViewModel。

修改你的ListView代码如下:

struct ListView: View {
    let animals = ["漣", "璉", "煉", "廉", "曆"]
    @State private var selectedAnimal: String?
    
    var body: some View {
        List {
            ForEach(animals, id: \.self) { animal in
                // 用Button触发选中状态,替代直接用NavigationLink
                Button(action: { selectedAnimal = animal }) {
                    Text(animal)
                }
            }
        }
        // 关联选中状态,只有当selectedAnimal有值时才创建目标视图
        .navigationDestination(item: $selectedAnimal) { animal in
            DetailView(viewModel: DetailViewModel(text: animal))
        }
    }
}

这个写法的好处是:

  • 完全避免了提前初始化所有DetailView和ViewModel
  • 代码结构更清晰,把导航目标的创建逻辑和列表项的展示逻辑分离开
  • 支持更灵活的导航控制(比如可以通过代码触发导航)

方案2:兼容iOS 15及以下——自定义LazyView延迟加载

如果需要兼容更低版本的iOS,可以通过自定义LazyView来包装目标视图,把视图的初始化延迟到真正需要的时候(也就是用户点击导航链接时)。

首先定义LazyView:

struct LazyView<Content: View>: View {
    let build: () -> Content
    
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    
    var body: some View {
        build()
    }
}

然后修改ListView中的NavigationLink:

struct ListView: View {
    let animals = ["漣", "璉", "煉", "廉", "曆"]
    
    var body: some View {
        List {
            ForEach(animals, id: \.self) { animal in
                NavigationLink(
                    // 用LazyView包裹目标视图,延迟初始化
                    destination: LazyView(DetailView(viewModel: DetailViewModel(text: animal)))
                ) {
                    Text(animal)
                }
            }
        }
    }
}

原理是:LazyView的body只有在被渲染时才会执行build()闭包,而NavigationLink只有在用户点击并开始导航时,才会渲染LazyView的body,这样就避免了提前创建所有ViewModel。

额外说明:为什么原写法会提前初始化?

SwiftUI在计算视图的body时,会解析整个视图结构的描述——包括NavigationLink的destination参数。原写法中,你直接传入了DetailViewModel(text: animal),这会在body计算时就立即执行初始化,导致所有列表项对应的ViewModel都被提前创建。而上面的两种方案,都是把初始化逻辑包装成了闭包,延迟到真正需要的时候才执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 07:57:28