SwiftUI中使用@Environment尺寸变量时的无限循环问题
SwiftUI尺寸类环境变量引发ViewModel反复销毁的无限循环问题
问题现象
在SwiftUI视图中引入horizontalSizeClass或verticalSizeClass环境变量后,将设备从竖屏切换为横屏再转回竖屏,会出现以下异常:
TestPlayerVM的deinit方法被反复调用,ViewModel不断销毁重建- 视图的
body陷入无限循环重复执行 - 移除尺寸类环境引用,或者跳过
TestPlayerVM初始化时的addPlayerObservers调用,该问题会消失
问题原因
核心问题在于**PlayerView中用@State修饰TestPlayerVM的错误做法**,结合尺寸类变化触发的视图重建逻辑导致:
- 尺寸类变化会触发父视图
InfiniteLoopView的body重建,环境变化会传递给子视图PlayerView,引发子视图的状态重置 @State是为值类型设计的状态容器,用于引用类型(Observable对象)时,无法保证实例的生命周期稳定性,视图重建时会重新初始化ViewModel- AVPlayer的订阅(
player.publisher)持有ViewModel的引用,与视图重建的逻辑相互作用,形成ViewModel反复创建销毁的循环
解决方案
方案1:改用@StateObject管理ViewModel(推荐)
@StateObject是SwiftUI专门为Observable对象设计的状态容器,能确保ViewModel在视图的整个生命周期内只初始化一次,不会因视图重建而销毁:
struct PlayerView: View { @Environment(\.dismiss) var dismiss var playerURL:URL // 将@State替换为@StateObject @StateObject var playerVM = TestPlayerVM() var body: some View { VideoPlayer(player: playerVM.player) .ignoresSafeArea() .background(Color.black) .task { let playerItem = AVPlayerItem(url: playerURL) playerVM.playerItem = playerItem } } }
这个修改从根源上解决了ViewModel反复销毁的问题,是SwiftUI中管理Observable对象的标准做法。
方案2:优化订阅避免潜在循环引用(辅助)
如果暂时无法替换@State,可以在订阅时添加weak self避免潜在的循环引用,缓解问题:
private func addPlayerObservers() { player.publisher(for: \.timeControlStatus, options: [.initial, .new]) .receive(on: DispatchQueue.main) .sink { [weak self] timeControlStatus in guard self != nil else { return } print("Player time control status \(timeControlStatus)") } .store(in: &cancellable) }
注意:此方法仅为辅助手段,无法彻底解决视图重建导致的ViewModel重置问题,优先使用方案1。
总结
- 尺寸类变化会触发SwiftUI视图的环境更新与
body重计算,必须用正确的状态容器管理引用类型的生命周期 @StateObject用于Observable对象的生命周期管理,@State仅适用于值类型- 使用AVPlayer等系统框架对象时,确保ViewModel的稳定性是避免资源泄漏和异常行为的关键
内容的提问来源于stack exchange,提问作者Deepak Sharma
相关产品推荐
相关产品推荐

