You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 22:42:34