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

Swift异步封装遗留回调API:寻找类Task的FutureIsh类型

将遗留Completion闭包API封装为Async/Await风格的Future类型

问题描述

我有一个遗留API函数,返回UIViewController并接收一个completion闭包,视图控制器展示后会调用该闭包传入结果:

func makeController(completion: @escaping (ViewResult) -> Void) -> UIViewController

我希望将其封装为更易用的异步API,形式如下:

func makeController() -> (UIViewController, FutureIsh<ViewResult>)

其中FutureIsh需要支持类似Task的await futureish.value调用方式。我想知道有没有合适的系统原生/现有类型可以用,不想自己用Actor实现。

另外我尝试用Task实现,但系统没有taskWithAPromise()方法,想确认这个思路是否可行:

func makeController() -> (UIViewController, Task<ControllerResult>) {
  // 该方法不存在,且我无法通过withCheckedContinuation创建它
  let task = Task<ControllerResult, Never>.taskWithAPromise()

  // 调用遗留API…
  let controller = makeController(completion: task.promise)

  return (controller, task)
}

解决方案

一、系统原生可用的「FutureIsh」类型

1. Task + CheckedContinuation(推荐,无额外依赖)

系统原生的Task虽然需要工作闭包,但可以通过withCheckedContinuation模拟出promise式的逻辑,完全符合你的需求:

func makeController() -> (UIViewController, Task<ViewResult, Never>) {
    // 定义一个可选的continuation,用于在completion触发时恢复任务
    var continuation: CheckedContinuation<ViewResult, Never>?
    
    // 创建控制器,将completion绑定到continuation的resume操作
    let controller = makeController { [weak continuation] result in
        continuation?.resume(returning: result)
    }
    
    // 创建Task,内部挂起等待continuation被resume
    let resultTask = Task {
        await withCheckedContinuation { cont in
            continuation = cont
        }
    }
    
    return (controller, resultTask)
}

调用方式:

let (vc, resultTask) = makeController()
present(vc, animated: true)
// 等待视图控制器返回结果
let result = await resultTask.value
  • 优势:完全基于系统API,不需要引入第三方库,调用方式和Task一致。
  • 注意:添加[weak continuation]避免循环引用(如果控制器持有completion闭包的话)。

2. Combine的Future类型

如果项目已经使用Combine,可以用Combine的Future(本身就是promise式设计),并通过AsyncSequence转成await调用:

import Combine

func makeController() -> (UIViewController, AnyPublisher<ViewResult, Never>) {
    var promise: Future<ViewResult, Never>.Promise?
    
    let controller = makeController { [weak promise] result in
        promise?(.success(result))
    }
    
    let publisher = Future<ViewResult, Never> { promise = $0 }
        .eraseToAnyPublisher()
    
    return (controller, publisher)
}

调用方式:

let (vc, publisher) = makeController()
present(vc, animated: true)
let result = await publisher.values.first!

二、关于taskWithAPromise()思路的可行性

系统原生Task确实没有这个方法,但你想要的“通过promise触发任务结果”的逻辑,已经可以通过Task + CheckedContinuation实现,本质就是把continuation当作promise传递给遗留API的completion。这个思路完全可行,只是需要用系统提供的CheckedContinuation手动实现promise的绑定逻辑。

关键注意事项

  • 避免循环引用:如果控制器会持有completion闭包,一定要在闭包里用弱引用捕获continuation/promise,防止内存泄漏。
  • 确保单次resume:遗留API保证只会调用completion一次,所以不用担心多次resume的问题;如果有多次调用风险,需要用withCheckedThrowingContinuation并处理重复调用的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 07:02:47