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

Swift:如何等待Delegate回调完成?第三方SDK异步转同步问询

解决方案:用信号量实现同步等待回调结果

这是个典型的「回调地狱转同步流程」场景,咱们可以借助DispatchSemaphore在单线程环境下阻塞等待第三方SDK的回调,同时改造TheirServiceDelegate的实现来捕获每个步骤的成功/失败状态,把异步回调转换成清爽的同步函数调用。下面一步步拆解实现:

1. 改造MyClient核心结构

首先在MyClient中添加几个关键成员:用于阻塞等待的信号量、存储步骤错误的变量、存储最终结果的变量,同时给第三方SDK绑定Delegate:

class MyClient: TheirServiceDelegate {
    static let instance = MyClient()
    let myService = TheirService()
    // 信号量:初始值为0,用来阻塞当前线程直到回调触发
    private let semaphore = DispatchSemaphore(value: 0)
    // 存储当前步骤的错误信息(用于抛出异常)
    private var currentStepError: Error?
    // 存储最终业务结果
    private var finalUsefulResult: SomeObject?
    
    private init() {
        // 给第三方SDK绑定当前类作为Delegate
        myService.delegate = self
    }
    
    // MARK: - 同步流程方法
    func connect(user: String, password: String) throws {
        // 每个步骤开始前重置状态,避免上一次的结果/错误干扰
        resetFlowState()
        
        // 执行初始化
        myService.initialize()
        // 等待回调触发,设置10秒超时防止无限阻塞
        if semaphore.wait(timeout: .now() + 10) == .timedOut {
            throw CustomFlowError.timeout
        }
        // 如果有错误,直接抛出
        if let error = currentStepError {
            throw error
        }
        
        // 执行bootstrap
        resetFlowState()
        myService.bootstrap()
        semaphore.wait(timeout: .now() + 10)
        if let error = currentStepError {
            throw error
        }
        
        // 执行登录
        resetFlowState()
        myService.login(user, password)
        semaphore.wait(timeout: .now() + 10)
        if let error = currentStepError {
            throw error
        }
    }
    
    func doSomethingUseful() throws -> SomeObject {
        resetFlowState()
        
        // 执行步骤1
        myService.doSomethingUsefulStep1()
        semaphore.wait(timeout: .now() + 10)
        if let error = currentStepError {
            throw error
        }
        
        // 执行步骤2
        resetFlowState()
        myService.doSomethingUsefulStep2()
        semaphore.wait(timeout: .now() + 10)
        if let error = currentStepError {
            throw error
        }
        
        // 返回最终结果,无结果则抛出异常
        guard let result = finalUsefulResult else {
            throw CustomFlowError.noResultReturned
        }
        return result
    }
    
    // MARK: - 辅助方法
    private func resetFlowState() {
        currentStepError = nil
        finalUsefulResult = nil
        // 信号量的计数会被wait消耗,这里无需额外重置
    }
    
    // MARK: - TheirServiceDelegate 回调实现
    func onInitializeSuccess() {
        // 成功则直接唤醒等待的线程
        semaphore.signal()
    }
    
    func onInitializeFailure(error: Error) {
        currentStepError = error
        semaphore.signal()
    }
    
    func onBootstrapSuccess() {
        semaphore.signal()
    }
    
    func onBootstrapFailure(error: Error) {
        currentStepError = error
        semaphore.signal()
    }
    
    func onLoginSuccess() {
        semaphore.signal()
    }
    
    func onLoginFailure(error: Error) {
        currentStepError = error
        semaphore.signal()
    }
    
    func onDoSomethingUsefulStep1Success() {
        semaphore.signal()
    }
    
    func onDoSomethingUsefulStep1Failure(error: Error) {
        currentStepError = error
        semaphore.signal()
    }
    
    func onDoSomethingUsefulStep2Success(result: SomeObject) {
        finalUsefulResult = result
        semaphore.signal()
    }
    
    func onDoSomethingUsefulStep2Failure(error: Error) {
        currentStepError = error
        semaphore.signal()
    }
}

// 自定义错误类型,方便统一抛出流程异常
enum CustomFlowError: Error {
    case timeout
    case noResultReturned
}

2. 关键逻辑说明

  • 信号量的核心作用:每次调用第三方SDK方法后,semaphore.wait()会阻塞当前线程,直到Delegate回调触发semaphore.signal(),线程才会继续执行下一个步骤,完美适配第三方SDK的单线程串行要求。
  • 状态重置:每个步骤开始前调用resetFlowState()清空错误和结果,避免上一次流程的状态污染当前流程。
  • 超时保护:给wait设置超时时间,防止第三方SDK回调异常导致线程无限阻塞。
  • 错误传递:在Delegate的失败回调中存储错误,唤醒线程后在同步方法中检查并抛出异常,实现错误的向上传递。

3. 调用方式

现在你可以完全按照期望的同步方式调用:

do {
    try MyClient.instance.connect(user: "your_username", password: "your_password")
    let usefulResult = try MyClient.instance.doSomethingUseful()
    // 处理业务结果
    print("获取到结果:\(usefulResult)")
} catch {
    // 统一处理所有流程错误
    print("流程执行失败:\(error)")
}

注意事项

  • 务必在非主线程调用connect()和doSomethingUseful(),如果在主线程调用semaphore.wait()会导致UI卡死。
  • 如果第三方SDK的回调是在主线程触发的,信号量的signal()可以正常唤醒等待的后台线程,不会有冲突。
  • 可以根据业务场景调整超时时间,或者在极端情况下去掉超时(不推荐,避免线程卡死)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:10:31