iOS开发中调用capturePhoto(with:, delegate:)后如何确保代理方法执行以正确获取CIImage
解决AVCapturePhotoOutput代理方法异步执行导致的空值问题
你遇到的这个问题核心是AVCapturePhotoOutput的拍照流程是异步的:调用capturePhoto(with:delegate:)后,系统会在后台完成照片捕获、编码等操作,只有当这些操作全部完成时,才会触发didFinishProcessingPhoto代理方法。而你在captureButton里同步调用getCGRectInfo(),此时代理方法还没执行,image变量自然是空的,直接访问就会崩溃。
下面给你两种实用的解决方案,都是为了确保在照片处理完成后再执行后续逻辑:
方案一:使用闭包回调(最直接的UIKit风格)
我们可以给captureProcess的capturePhoto方法添加一个完成回调,让代理方法执行完成后再触发后续操作:
1. 修改captureProcess类
import Foundation import AVFoundation // 注意:Swift类名建议用大驼峰,改成CaptureProcess更规范 class CaptureProcess: NSObject, AVCapturePhotoCaptureDelegate{ var image: CIImage? // 用于保存回调闭包的私有变量 private var completionHandler: ((CIImage?, Error?) -> Void)? func capturePhoto(_ photoOutput: AVCapturePhotoOutput, completion: @escaping (CIImage?, Error?) -> Void) { let settings = AVCapturePhotoSettings() settings.flashMode = .off // 保存回调闭包 self.completionHandler = completion photoOutput.capturePhoto(with: settings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { // 用defer确保回调后清空闭包,避免循环引用 defer { completionHandler = nil } guard let imageData = photo.fileDataRepresentation() else { // 传递错误信息 completionHandler?(nil, error ?? NSError(domain: "CaptureError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取照片数据"])) return } let ciImage = CIImage(data: imageData) self.image = ciImage // 触发回调,传递处理好的CIImage completionHandler?(ciImage, error) } }
2. 在ViewController中调用
@IBAction func captureButton(_ sender: Any) { captureProcessor.capturePhoto(capturedPhotoOutPut) { [weak self] ciImage, error in // 用weak self避免循环引用 guard let self = self, let image = ciImage else { // 处理错误,比如给用户提示 if let error = error { print("拍照失败:\(error.localizedDescription)") } return } // 现在可以安全调用getCGRectInfo(),因为image已经初始化完成 self.getCGRectInfo() // 也可以直接在这里更新UI,记得确保在主线程(闭包默认在后台线程执行) DispatchQueue.main.async { self.imageShow.image = UIImage(ciImage: image) } } }
方案二:使用Combine响应式编程(更现代的Swift风格)
如果你的项目支持Combine框架,可以通过发布订阅的方式监听image变量的变化:
1. 修改captureProcess类
import Foundation import AVFoundation import Combine class CaptureProcess: NSObject, AVCapturePhotoCaptureDelegate{ // 用@Published标记变量,自动生成发布者 @Published var image: CIImage? func capturePhoto(_ photoOutput: AVCapturePhotoOutput) { let settings = AVCapturePhotoSettings() settings.flashMode = .off photoOutput.capturePhoto(with: settings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let imageData = photo.fileDataRepresentation() { image = CIImage(data: imageData) } } }
2. 在ViewController中订阅变化
import UIKit import AVFoundation import Combine class ViewController: UIViewController { let captureProcessor = CaptureProcess() let session = AVCaptureSession() let capturedPhotoOutPut = AVCapturePhotoOutput() @IBOutlet weak var previewOfCamera: UIView! @IBOutlet weak var imageShow: UIImageView! // 用于保存订阅对象,防止被释放 private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() configurePreview() // 订阅image变量的变化 captureProcessor.$image // 切换到主线程更新UI .receive(on: DispatchQueue.main) // 过滤掉空值 .compactMap { $0 } .sink { [weak self] ciImage in // 当image有值时,执行后续逻辑 self?.getCGRectInfo() self?.imageShow.image = UIImage(ciImage: ciImage) } .store(in: &cancellables) } @IBAction func captureButton(_ sender: Any) { captureProcessor.capturePhoto(capturedPhotoOutPut) } // ... 其他原有代码 ... }
额外注意事项
- 线程安全:AVCapture的代理方法默认在后台线程执行,所以更新UI时一定要切换到主线程(方案一用
DispatchQueue.main.async,方案二用receive(on: DispatchQueue.main))。 - 错误处理:不要忽略
error参数,拍照可能因为权限、设备故障等原因失败,处理错误能提升用户体验。 - 命名规范:Swift中类名、方法名建议用大驼峰命名(比如
captureProcess改成CaptureProcess),代码可读性更高。
内容的提问来源于stack exchange,提问作者DifficultToName
相关产品推荐
相关产品推荐

