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

如何在Swift中用Async/Await适配带void*上下文的C回调?

用Async/Await替代DispatchSemaphore适配带void*回调的C库

要将基于DispatchSemaphore的同步等待逻辑改为Async/Await风格,核心是利用Swift的CheckedThrowingContinuation桥接C回调API,同时处理内存安全和超时逻辑。以下是改造后的实现方案:

基础实现(无需传递Test实例为C上下文)

class Test {
    var sessionStarted: Bool = false
    private var sessionContinuation: CheckedContinuation<Void, Error>?

    // 改为async throws方法,调用方通过try await等待会话建立完成
    func startSession() async throws {
        guard !sessionStarted else {
            print("Session already started")
            return
        }

        // C回调直接捕获weak self,处理会话事件并唤醒continuation
        let callback: KMSessionEventCallback = { [weak self] inSession, inEvent, _ in
            guard let self = self, let session = inSession else { return }
            
            switch inEvent {
            case KM_SESSION_ATTACHED:
                self.sessionStarted = true
                self.sessionContinuation?.resume() // 成功完成,唤醒异步任务
            case KM_SESSION_TERMINATED, KM_SESSION_FAILED, KM_SESSION_DETACHED:
                self.sessionStarted = false
                self.sessionContinuation?.resume(throwing: SessionError.attachFailed) // 抛出失败错误
            default:
                print("Unknown State")
                return // 忽略未知事件,不触发continuation
            }
            
            // 清空continuation,避免重复调用
            self.sessionContinuation = nil
        }

        let result = KMSessionAttach("SessionTest", &callback, nil)
        guard result == KM_SUCCESS else {
            throw SessionError.attachFailed
        }

        // 用Continuation桥接回调,同时启动超时任务
        try await withCheckedThrowingContinuation { [weak self] continuation in
            self?.sessionContinuation = continuation
            
            // 30秒超时后主动抛出错误
            Task {
                try await Task.sleep(nanoseconds: 30 * 1_000_000_000)
                self?.sessionContinuation?.resume(throwing: SessionError.timeout)
                self?.sessionContinuation = nil
            }
        }
    }

    // 自定义会话错误类型,明确错误场景
    enum SessionError: Error {
        case attachFailed
        case timeout
    }
}

关键改动说明

  • 异步方法改造:将startSession转为async throws类型,调用方只需通过try await test.startSession()即可等待会话建立完成,无需手动处理信号量。
  • Continuation桥接:使用withCheckedThrowingContinuation将C风格的回调API转换为Async/Await范式,通过暂存sessionContinuation对象,等待回调触发后唤醒异步任务。
  • 回调逻辑优化:直接在C回调内处理会话事件,根据事件结果调用resume()(成功)或resume(throwing:)(失败),简化原有的handleSessionUpdate中转逻辑。
  • 超时处理:通过独立的Task.sleep任务实现30秒超时机制,超时后主动抛出错误并清空continuation,避免无限等待。
  • 内存安全保障:回调中使用[weak self]捕获Test实例,避免循环引用导致的内存泄漏。

强制传递void*上下文的场景处理

如果C库要求必须传递void*类型的上下文参数,可以通过自定义上下文结构体+Swift内存管理API实现:

class Test {
    var sessionStarted: Bool = false

    func startSession() async throws {
        guard !sessionStarted else {
            print("Session already started")
            return
        }

        try await withCheckedThrowingContinuation { continuation in
            // 封装上下文:包含Test实例和continuation
            struct CallbackContext {
                weak var test: Test?
                let continuation: CheckedContinuation<Void, Error>
            }
            let context = CallbackContext(test: self, continuation: continuation)
            // 将上下文转为void*指针,传递给C库
            let contextPtr = Unmanaged.passRetained(context).toOpaque()

            let callback: KMSessionEventCallback = { inSession, inEvent, inContext in
                guard let contextPtr = inContext, let session = inSession else { return }
                // 从void*恢复上下文,并释放内存
                let context = Unmanaged<CallbackContext>.fromOpaque(contextPtr).takeRetainedValue()
                guard let self = context.test else { return }
                
                switch inEvent {
                case KM_SESSION_ATTACHED:
                    self.sessionStarted = true
                    context.continuation.resume()
                case KM_SESSION_TERMINATED, KM_SESSION_FAILED, KM_SESSION_DETACHED:
                    self.sessionStarted = false
                    context.continuation.resume(throwing: SessionError.attachFailed)
                default:
                    print("Unknown State")
                    return
                }
            }

            let result = KMSessionAttach("SessionTest", &callback, contextPtr)
            guard result == KM_SUCCESS else {
                // 若初始化失败,需手动释放上下文内存并抛出错误
                Unmanaged<CallbackContext>.fromOpaque(contextPtr).release()
                continuation.resume(throwing: SessionError.attachFailed)
                return
            }
        }
    }

    enum SessionError: Error {
        case attachFailed
        case timeout
    }
}

这种方式通过Unmanaged管理自定义上下文的内存,确保在回调触发或初始化失败时都能正确释放内存,避免泄漏。


内容的提问来源于stack exchange,提问作者Richard Small

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 03:31:06