SwiftUI VideoPlayer内存泄漏问题(@State管理异常)
循环视频播放器的内存与状态管理问题
我尝试基于AVKit的VideoPlayer实现一个循环视频播放器视图,代码如下:
VideoPlayerView 实现
struct VideoPlayerView: View { let video: Video let isPlaying: Bool init(video: Video, isPlaying: Bool) { Self.logger.info("VideoPlayerView.init: \(video.name)") self.video = video self.isPlaying = isPlaying if let url = self.video.url { let playerItem = AVPlayerItem(url: url) self._player = State(initialValue: AVQueuePlayer(playerItem: playerItem)) self._looper = State(initialValue: AVPlayerLooper(player: self.player!, templateItem: playerItem)) } } var body: some View { let _ = print("VideoPlayerView body requested: “\(self.video.name)” [\(self.namespace)]", terminator: " -- ") let _ = Self._printChanges() VStack { if let player = self.player { VideoPlayer(player: player) } else { Text("Unable to create player.") } } .onAppear() { Self.logger.info("Player appeared: \(self.video.name)") self.isPlaying ? self.player?.play() : self.player?.pause() } .onDisappear() { Self.logger.info("Player disappeared: \(self.video.name)") self.player?.pause() self.player?.seek(to: .zero) } .onChange(of: self.isPlaying) { inOld, inNew in Self.logger.info("isPlaying changed: \(inNew)") inNew ? self.player?.play() : self.player?.pause() } } @State private var player: AVQueuePlayer? @State private var looper: AVPlayerLooper? @Namespace private var namespace static let logger = Logger(subsystem: "<>", category: "VideoPlayerView") }
父视图中使用方式
作为NavigationSplitView详情页的一部分,根据选中项实例化:
detail: { let sortedSelection = self.selection.sorted(using: SortDescriptor(\.lastViewed)) if sortedSelection.count > 0 { ScrollView { VStack { ForEach(sortedSelection) { video in VideoPlayerView(video: video, isPlaying: self.isPlaying) .id(video) .aspectRatio(4.0/3.0, contentMode: .fit) } } } } else { Text("Select an Item") } }
Video 数据模型
@Model final class Video { var name: String var bookmark: Data var lastViewed: Date? var url: URL? { // 从bookmark获取URL的逻辑 } }
遇到的问题
- 播放视频时切换选中项,原视频视图消失后仍能听到音频(添加
onDisappear的pause()后有所改善,但未彻底解决) - 每次状态变更(如播放/暂停切换)都会创建新的AVFoundation对象,不确定
@State在此场景下是否会丢弃初始值 - 内存占用持续上升,每次启停视频都会创建新线程,内存无法有效释放
尝试过在onAppear中初始化状态、onDisappear中将状态置为nil,但内存增长和线程创建问题仍存在,找不到更好的播放器状态管理方式。
解决方案
1. 禁止在init中初始化@State变量
@State变量的生命周期由SwiftUI管理,手动在init中赋值_player和_looper会绕过SwiftUI的状态回收机制,导致旧的AVFoundation对象无法被正确释放,重复创建新对象。
修改方式:把播放器初始化逻辑移到onAppear,并添加判断避免重复创建:
struct VideoPlayerView: View { let video: Video let isPlaying: Bool @State private var player: AVQueuePlayer? @State private var looper: AVPlayerLooper? // ... 其他属性 var body: some View { VStack { // ... 视图内容 } .onAppear() { guard player == nil, let url = video.url else { return } let playerItem = AVPlayerItem(url: url) let queuePlayer = AVQueuePlayer(playerItem: playerItem) self.player = queuePlayer self.looper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem) // 控制播放状态 self.isPlaying ? queuePlayer.play() : queuePlayer.pause() } // ... 其他修饰符 } }
2. 彻底清理AVFoundation资源
在onDisappear中不仅要暂停播放,还要主动销毁播放器和循环器,断开所有引用,让ARC能回收底层资源:
.onDisappear() { player?.pause() player?.seek(to: .zero) // 销毁循环器和播放器,释放底层资源 looper = nil player = nil }
3. 用@StateObject封装播放器逻辑
把播放器的创建、播放控制、资源清理逻辑抽离到独立的ObservableObject类中,让状态管理更清晰,也能更精准地控制资源生命周期:
class VideoPlayerManager: ObservableObject { private(set) var player: AVQueuePlayer? private var looper: AVPlayerLooper? let video: Video init(video: Video) { self.video = video } func preparePlayer() { guard player == nil, let url = video.url else { return } let playerItem = AVPlayerItem(url: url) let queuePlayer = AVQueuePlayer(playerItem: playerItem) self.player = queuePlayer self.looper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem) } func play() { player?.play() } func pause() { player?.pause() player?.seek(to: .zero) } func cleanup() { pause() looper = nil player = nil } }
然后在VideoPlayerView中使用:
struct VideoPlayerView: View { let video: Video let isPlaying: Bool @StateObject private var playerManager: VideoPlayerManager init(video: Video, isPlaying: Bool) { self.video = video self.isPlaying = isPlaying self._playerManager = StateObject(wrappedValue: VideoPlayerManager(video: video)) } var body: some View { VStack { if let player = playerManager.player { VideoPlayer(player: player) } else { Text("Unable to create player.") } } .onAppear() { playerManager.preparePlayer() isPlaying ? playerManager.play() : playerManager.pause() } .onDisappear() { playerManager.cleanup() } .onChange(of: isPlaying) { newValue in newValue ? playerManager.play() : playerManager.pause() } } }
4. 优化父视图的资源占用
如果选中项频繁切换,可考虑限制同时存在的播放器数量——比如只给当前可见的播放器初始化资源,避免一次性创建过多AVPlayer实例,进一步降低内存和线程开销。
关键逻辑说明
@State变量必须由SwiftUI初始化,手动赋值会破坏状态的持久化与回收逻辑- AVPlayer和AVPlayerLooper持有底层音频/视频资源,必须显式置nil才能触发系统回收
- 封装播放器逻辑到独立对象中,能更精准地控制资源的创建与销毁时机
内容的提问来源于stack exchange,提问作者Rick
相关产品推荐
相关产品推荐

