Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift async externally fulfilled Task / Future like type

I have a legacy API function that will provide a UIViewController. It takes as an argument a completion function. The view controller will call the completion function once after being presented with a result value.

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

I would like to wrap this in a slightly nicer API for async so that it becomes something like this:

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

Where the FutureIsh, would have a caller facing interface similar to Task so that you can await futureish.value.

I'm wondering if there is an existing suitable type FutureIsh? I don't think I can use Task because its work block has to be given to it. I think I can make something with an actor, but if there's an existing type I'd prefer to use it.


Attempting to write this with Task, perhaps I could do…

func makeController() -> (UIViewController, Task<ControllerResult>) {
  // This doesn't exist, and I can't see it is possible to create
  // it with `withCheckedContinuation`.
  let task = Task<ControllerResult, Never>.taskWithAPromise()

  // Use the legacy API…
  let controller = makeController(completion: task.promise)

  return (controller, task)
}
like image 413
Benjohn Avatar asked Dec 21 '25 06:12

Benjohn


1 Answers

I think you can use an AsyncStream that only produces one elemen.

AsyncStream.Continuation is allowed to escape. You can use makeStream to get an "escaped" continuation and an AsyncStream.

Assuming ViewResult is Sendable, you can do

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

func makeController() -> (UIViewController, Task<ViewResult, Never>) {
    let (stream, continuation) = AsyncStream.makeStream(of: ViewResult.self)
    
    // Here we wrap the AsyncStream into a Task
    // you can also just return the AsyncStream directly and let the caller consume it,
    // or Task.detached depending on what you need
    let task = Task {
       var iterator = stream.makeAsyncIterator()
        return await iterator.next()!
    }
    return (makeController {
        continuation.yield($0)
        continuation.finish()
    }, task)
}

Usage:

let (vc, task) = makeController()
print(vc)
print(await task.value)
like image 72
Sweeper Avatar answered Dec 23 '25 23:12

Sweeper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!