1

I'm trying to implement a function which returns an AsyncStream of BlurredImage objects. The functions relies on another function sourceImages(), which itself is an AsyncStream.

I get this error on line 2 of my snippet:

Cannot pass function of type '(AsyncStream<BlurredImage>.Continuation) async -> Void' to parameter expecting synchronous function type

What's the correct way to implement this?

    func blurredFaces() -> AsyncStream<BlurredImage> {
        return AsyncStream<BlurredImage> { continuation in
            for await image in sourceImages() {
                blurrImage(image) { blurredImage in
                    continuation.yield(blurredImage)
                }
            }
            continuation.finish()
        }
    }

    func sourceImages() -> AsyncStream<SourceImage> {
    ...
    }

I'm on Xcode 13.4.1

iljawascoding
  • 1,090
  • 9
  • 20
  • The present tense of `blurred` is `blur`. The other `r` is an anomaly encountered only when time traveling backwards. –  Jul 07 '22 at 11:21

2 Answers2

3

AsyncStream.init expects an input of type (AsyncStream<BlurredImage>.Continuation) → Void, which is not async.

But you can wrap its body in a Task to do async operations.

return AsyncStream<BlurredImage> { continuation in
    Task {
        for await image in sourceImages() {
            blurrImage(image) { blurredImage in
                continuation.yield(blurredImage)
            }
        }
        continuation.finish()
    }
}

Or as @Sweeper suggested, you can use map. Based on your blurredFaces return type signature, you want to map AsyncStream<SourceImage> (result of sourceImages) to AsyncStream<BlurredImage>.

In case for some reason you want to keep its return type as that, I haven't found a built-in direct map from an AsyncStream<T1> to an AsyncStream<T2>, so I posted this related SO question. In this answer, I shared the AsyncStream extension that I created for this use case. I am still waiting for feedbacks for this solution.

bzyr
  • 289
  • 1
  • 11
2

You seem to be doing an mapping operation here. In that case, you can use map and return an AsyncMapSequence:

func blurredFaces() -> AsyncMapSequence<AsyncStream<SourceImage>, BlurredImage> {
    sourceImages().map { image in
        await withCheckedContinuation { continuation in
            blurrImage(image) { blurred in
                continuation.resume(returning: blurred)
            }
        }
    }
}

If you can make blurrImage async, then this can be even shorter:

func blurredFaces() -> AsyncMapSequence<AsyncStream<SourceImage>, BlurredImage> {
    // you can even inline this, and get rid of blurredFaces
    sourceImages().map(blurrImage)
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • `sourceImages().map(blurrImage)`. Doesn't seem like it warrants a method. –  Jul 07 '22 at 11:19
  • 1
    @Jessy I didn't know you could do that! :D I thought the `await` would prevent it. – Sweeper Jul 07 '22 at 11:23
  • While the code compiles, it stops at `await withCheckedContinuation` until all sourceImages() have been generated. I was under (the possibly wrong) impression, that after the sourceImages async sequence had produced the first image, the blurrImage code would be executed. – iljawascoding Jul 07 '22 at 12:14
  • @iljawascoding How have you implemented `sourceImages`? Depending on your implementation, it is possible for the fetching of the next image to not wait for the blurring of the previous image to finish. If the fetching is also very quick compared to the blurring, it may *seem* like all the fetching is done first, before the blurring starts. – Sweeper Jul 07 '22 at 12:55
  • I use `continuation.yield(image)` in the method that produces the source images. Here is the complete code: https://github.com/iljaiwas/FaceBlurrer – iljawascoding Jul 07 '22 at 13:55
  • 1
    @iljawascoding Yeah I think what I said in my comment is pretty much what's happening here, except `imageGenerator.copyCGImage` isn't even asynchronous. You can put that in an async function and await it inside the for loop, then put the entire for loop inside a `Task`, if this is a problem for you. If you have trouble getting this to work, show a [mcve] and post a new question :) – Sweeper Jul 07 '22 at 14:18