如何在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
相关产品推荐
相关产品推荐

