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

