使用@Published时CPU占用过高,MTKView渲染卡顿如何解决?
问题分析与解决方案
核心问题1:重复启动接收线程导致帧请求堆积
你的ContentView在navigationDestination中再次创建了ContentView(),每次新视图出现都会调用ndi_c.StartRecv(),而NDI是单例,这会导致多个后台while循环同时运行,不断向主线程发送帧更新请求,直接造成主线程过载。
核心问题2:无限制的主线程更新
即使只启动一次接收,60fps的帧会无间隔地通过DispatchQueue.main.async发送更新请求,主线程来不及处理所有请求,导致任务堆积、帧率波动。
核心问题3:SwiftUI视图频繁重绘
@Published的NDI_Frame每次更新都会触发NDIView的重绘,频繁重绘会进一步增加主线程负担。
具体修复步骤
1. 限制接收线程只启动一次
修改NDI类的StartRecv(),添加启动检查:
func StartRecv(){ guard !Connected else { return } // 已在接收则直接返回,避免重复启动 Connected = ndi_t!.startRecv(SelectedSource ?? "") NDI_Queue.async { while(self.Connected){ if let buf = self.PrepareFrame(){ let ciImage = CIImage(cvPixelBuffer: buf) self.frameSubject.send(ciImage) } } } }
同时添加停止接收的方法:
func StopRecv(){ Connected = false ndi_t?.stopRecv() }
在ContentView的NDIView上添加onDisappear停止接收:
NDIView(imagePublisher: self.ndi_c.$NDI_Frame.eraseToAnyPublisher()) .onAppear(){ ndi_c.StartRecv() ARData.shared.HideARView = NDIView_Top } .onDisappear(){ ndi_c.StopRecv() }
2. 控制主线程更新频率,避免任务堆积
改用Combine的throttle限制帧更新频率,确保主线程最多以60fps处理更新:
修改NDI类:
class NDI: ObservableObject{ private let frameSubject = PassthroughSubject<CIImage?, Never>() @Published var NDI_Frame: CIImage? let NDI_Queue = DispatchQueue(label: "NDI_recv", qos: .userInitiated) // 给后台线程设置合适优先级 static var shared = NDI() var ndi_t: NDIWrapper? var Connected: Bool = false init(){ ndi_t = NDIWrapper() // 每秒最多60次更新,只取最新帧,避免主线程被淹没 frameSubject .throttle(for: .seconds(1/60), scheduler: DispatchQueue.main, latest: true) .assign(to: &$NDI_Frame) } // ... 其他代码保持不变 func StartRecv(){ guard !Connected else { return } Connected = ndi_t!.startRecv(SelectedSource ?? "") NDI_Queue.async { while(self.Connected){ if let buf = self.PrepareFrame(){ let ciImage = CIImage(cvPixelBuffer: buf) self.frameSubject.send(ciImage) // 发送到subject,由throttle控制频率 } } } } }
3. 避免重复创建ContentView
你的navigationDestination中.MainView返回ContentView(),会创建新的视图实例,造成不必要的资源消耗。改为弹出到根视图替代:
case .MainView: navigationPath.removeLast(navigationPath.count) // 直接回到根视图,无需创建新实例
4. 优化MTKView渲染效率
确保NDIView的渲染逻辑完全在GPU上完成:
- 复用同一个
CIContext(用Metal设备创建),不要每次渲染都新建 - 渲染逻辑放在Metal的渲染循环中,不要依赖SwiftUI的重绘触发
额外优化建议
- 检查
CVPixelBuffer的格式是否与MTKView兼容,避免额外的格式转换开销 - 后台处理帧时尽量减少内存拷贝,优先复用缓冲区
- 用Instruments的Time Profiler工具查看主线程耗时任务,定位具体卡顿点
内容的提问来源于stack exchange,提问作者Shimamura Uzuki
相关产品推荐
相关产品推荐

