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

SwiftUI中如何等待StateObject类完成初始化?

解决SwiftUI中StateObject异步初始化的问题

首先明确:SwiftUI没有直接等待StateObject初始化的专属方法,但你可以通过拆分异步逻辑+状态驱动视图的方式解决这个问题,核心思路是把异步操作从初始化器里移出来,用@Published属性跟踪状态,让视图根据状态动态更新。

第一步:重构VideoStream类

因为NSObject的初始化器不能是异步的,所以把权限请求、Session配置这些异步逻辑从init里抽出来,放到单独的异步方法中,同时用@Published属性跟踪Session和当前状态:

import AVFoundation
import Combine

class VideoStream: NSObject, ObservableObject {
    // 用可选类型+@Published跟踪Session状态
    @Published var session: AVCaptureSession?
    @Published var luminosityReading: Double = 0.0
    // 定义状态枚举,跟踪初始化流程
    enum Status {
        case idle
        case requestingPermission
        case ready
        case denied
    }
    @Published var status: Status = .idle
    
    override init() {
        super.init()
        // 这里只做基础初始化,不处理异步逻辑
    }
    
    // 单独的异步方法处理权限和Session配置
    func setupSession() async {
        status = .requestingPermission
        
        // 1. 检查/请求相机权限
        let status = AVCaptureDevice.authorizationStatus(for: .video)
        switch status {
        case .authorized:
            await configureSession()
        case .notDetermined:
            let granted = await AVCaptureDevice.requestAccess(for: .video)
            if granted {
                await configureSession()
            } else {
                self.status = .denied
            }
        case .denied, .restricted:
            self.status = .denied
        @unknown default:
            self.status = .denied
        }
    }
    
    // 配置AVCaptureSession的异步方法
    private func configureSession() async {
        let newSession = AVCaptureSession()
        newSession.sessionPreset = .high
        
        // 这里写你的Session配置逻辑(添加输入输出、设置元数据代理等)
        // 示例:添加视频输入
        guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let input = try? AVCaptureDeviceInput(device: device) else {
            status = .denied
            return
        }
        
        if newSession.canAddInput(input) {
            newSession.addInput(input)
        }
        
        // 配置亮度元数据输出(示例)
        let metadataOutput = AVCaptureMetadataOutput()
        if newSession.canAddOutput(metadataOutput) {
            newSession.addOutput(metadataOutput)
            // 设置元数据代理,处理亮度数据
            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue(label: "LuminosityQueue"))
            metadataOutput.metadataObjectTypes = [.brightness]
        }
        
        newSession.startRunning()
        session = newSession
        status = .ready
    }
}

// 扩展实现AVCaptureMetadataOutputObjectsDelegate,处理亮度数据
extension VideoStream: AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        guard let brightnessObject = metadataObjects.first as? AVMetadataBrightnessObject else { return }
        DispatchQueue.main.async {
            self.luminosityReading = brightnessObject.brightness
        }
    }
}

第二步:修改ContentView,根据状态动态渲染

在视图的onAppear触发异步初始化方法,然后根据videoStream.status显示不同内容:

struct ContentView: View {
    @StateObject var videoStream = VideoStream()
    
    var body: some View {
        VStack(spacing: 20) {
            // 根据状态显示不同内容
            switch videoStream.status {
            case .idle, .requestingPermission:
                ProgressView("正在准备相机...")
                    .frame(width: 300, height: 300)
            case .ready:
                if let session = videoStream.session {
                    VideoPreviewHolder(runningSession: session)
                        .frame(width: 300, height: 300)
                }
            case .denied:
                Text("相机权限已拒绝,请在设置中开启")
                    .foregroundColor(.red)
                    .frame(width: 300, height: 300)
            }
            
            Text(String(format: "%.2f Lux", videoStream.luminosityReading))
                .font(.largeTitle)
        }
        .onAppear {
            // 视图出现时触发异步初始化
            Task {
                await videoStream.setupSession()
            }
        }
    }
}

// 保留你的VideoPreviewHolder实现
struct VideoPreviewHolder: UIViewRepresentable {
    let runningSession: AVCaptureSession
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: .zero)
        let captureLayer = AVCaptureVideoPreviewLayer(session: runningSession)
        captureLayer.frame = view.bounds
        captureLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(captureLayer)
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // 可以根据需要更新视图
    }
}

关键说明

  1. 为什么不能用异步初始化器?因为NSObject是Objective-C兼容类,而Objective-C没有异步初始化的概念,所以Swift不允许NSObject子类拥有async init,这就是你遇到报错的原因。
  2. 状态驱动:通过@Published的status属性,让SwiftUI自动感知状态变化,动态切换视图内容,避免在Session未初始化时传递空值给VideoPreviewHolder。
  3. 异步逻辑隔离:把权限请求、Session配置放到单独的异步方法中,用Task在onAppear里触发,既符合SwiftUI的生命周期,又能处理异步操作。

内容的提问来源于stack exchange,提问作者John Harrington

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 03:25:48