如何基于应用与视图全生命周期实现AVCaptureSession的最优处理?
这确实是iOS相机开发里一个容易让人纠结的点,我做过几个相机类应用,踩过不少坑,结合Apple官方文档的隐含逻辑和实际场景的验证,给你梳理下兼顾全生命周期的最优方案:
核心原则:延迟初始化 + 按需恢复 + 及时清理
相机资源属于系统敏感资源,系统会严格管控其使用,同时App生命周期的状态变化(后台/前台、视图显隐)都会影响AVCaptureSession的可用性,所以核心思路是不在视图初始化阶段硬塞所有逻辑,而是跟着视图和App的状态动态调整。
1. 权限检查:前置到viewDidLoad,但不初始化Session
viewDidLoad是最早可以发起相机权限请求的时机,但此时不要急着创建AVCaptureSession——因为权限还没确定,而且视图还没完成布局,预览层的尺寸也不准确。
示例代码:
override func viewDidLoad() { super.viewDidLoad() // 发起相机权限请求 AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in DispatchQueue.main.async { if granted { // 权限通过,标记状态,等待后续初始化 self?.hasCameraPermission = true } else { // 提示用户去设置开启权限 self?.showPermissionAlert() } } } // 监听权限变化通知(用户可能在设置里改权限) NotificationCenter.default.addObserver(self, selector: #selector(cameraPermissionChanged), name: NSNotification.Name.AVCaptureDeviceAuthorizationStatusDidChange, object: nil) }
2. Session初始化与启动:放在viewDidAppear
选择viewDidAppear而非viewWillAppear的原因:
- 此时视图已经完全显示,布局完成,预览层的bounds是准确的,不会出现预览画面拉伸/偏移的问题
- 用户已经看到相机界面,此时如果初始化失败,可以直接在界面上提示(比如“相机初始化失败,请重试”),体验更自然
具体步骤:
- 检查权限是否通过
- 创建
AVCaptureSession,配置输入(前后置相机)、输出(照片/视频) - 添加预览层到视图上
- 启动Session(注意要在异步队列执行,避免阻塞主线程)
示例代码:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) guard hasCameraPermission, !isSessionRunning else { return } DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self else { return } let session = AVCaptureSession() session.sessionPreset = .photo // 配置输入(以后置相机为例) guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), let input = try? AVCaptureDeviceInput(device: backCamera) else { DispatchQueue.main.async { self.showCameraErrorAlert() } return } // 配置输出 let photoOutput = AVCapturePhotoOutput() if session.canAddInput(input), session.canAddOutput(photoOutput) { session.addInput(input) session.addOutput(photoOutput) // 配置预览层 DispatchQueue.main.async { let previewLayer = AVCaptureVideoPreviewLayer(session: session) previewLayer.frame = self.previewView.bounds previewLayer.videoGravity = .resizeAspectFill self.previewView.layer.addSublayer(previewLayer) self.previewLayer = previewLayer } // 启动Session session.startRunning() self.captureSession = session self.isSessionRunning = true } } }
3. 后台/前台切换、视图重显:在viewWillAppear做恢复检查
当App进入后台时,系统会自动暂停AVCaptureSession,甚至在内存紧张时回收相机资源。所以每次视图即将显示时,需要检查Session状态并恢复:
- 如果Session存在但未运行,尝试重新启动
- 如果Session已被销毁(比如
captureSession为nil),则重新执行初始化逻辑 - 处理相机切换失败的情况:切换前后置相机时,必须先停止Session,再更换输入,然后重新启动——这个过程如果失败,要捕获错误并提示用户
示例代码:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) guard hasCameraPermission else { return } if let session = captureSession { if !session.isRunning { DispatchQueue.global(qos: .userInitiated).async { session.startRunning() self.isSessionRunning = true } } } else { // Session已销毁,重新初始化 setupCaptureSession() } }
4. 资源清理:在viewWillDisappear及时释放
当视图即将消失时,必须停止Session并清理资源,避免内存泄漏,同时也能让系统及时回收相机资源给其他App使用:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if let session = captureSession, session.isRunning { session.stopRunning() isSessionRunning = false } previewLayer?.removeFromSuperlayer() captureSession = nil }
5. 全局错误处理:监听Session状态变化
通过AVCaptureSession的代理方法或者KVO监听状态变化,处理初始化失败、启动失败、相机被占用等异常情况:
// 实现AVCaptureSessionDelegate func session(_ session: AVCaptureSession, didFailWithError error: Error) { DispatchQueue.main.async { self.showErrorAlert(message: "相机出错:\(error.localizedDescription)") // 标记Session失效,下次viewWillAppear时重新初始化 self.captureSession = nil self.isSessionRunning = false } }
总结最优流程
viewDidLoad:发起权限请求,监听权限变化和App生命周期通知viewDidAppear:权限通过时,初始化Session、配置输入输出、启动SessionviewWillAppear:检查Session状态,按需恢复或重新初始化viewWillDisappear:停止Session,清理预览层和Session实例- 代理/通知:全局捕获相机错误,提供重试提示
内容的提问来源于stack exchange,提问作者Thirsty4K
相关产品推荐
相关产品推荐

