I'm curious how this should interact with APIs that take both a completion handler and a dispatch queue to be used for that completion handler. My understanding is that current best practices are for asynchronous APIs to provide an argument for which queue the completion should run on to avoid the common pattern of needing to hop from queue to queue. For instance, if you want the completion to run on the main queue then it's wasteful to have to do a dispatch async onto the main queue in your completion block instead of just having that completion blocked put on the main queue to start with.
That said, I couldn't find many examples in the API diff linked above where the method actually takes both a completion block and a queue. One example is this method from the (deprecated) GLKTextureLoader
class:
func texture(withContentsOfFile path: String, options: [String : NSNumber]? = nil, queue: DispatchQueue?, completionHandler block: @escaping GLKTextureLoaderCallback)
func texture(withContentsOfFile path: String, options: [String : NSNumber]? = nil, queue: DispatchQueue?) async throws -> GLKTextureInfo
That new await
now takes a queue. So how does code like this behave?
let texture = await try loader.texture(withContentsOfFile:path options:nil queue:someQueue)
// Which queue am I on here?
Consider the answers for cases like when you start on the main queue and when you start on some other GCD queue.
Part of the answer might depend on the structured concurrency spec, but I'm not sure if this ObjC interop also affects the answer.
Also, should the interop try to smartly handle APIs like this where maybe they can use the task scheduling features from the structured concurrency spec to provide a good queue automatically. For instance, maybe instead we reflect this method like so:
func texture(withContentsOfFile path: String, options: [String : NSNumber]? = nil, queue: DispatchQueue?, completionHandler block: @escaping GLKTextureLoaderCallback)
func texture(withContentsOfFile path: String, options: [String : NSNumber]? = nil) async throws -> GLKTextureInfo
And then a call like this:
// Start on the main queue
let texture = await try loader.texture(withContentsOfFile:path options:nil) // main queue is implicitly passed in?
// Needs to be back on the main queue (as per structured concurrency spec)
Ideally you want this to not hop queues unnecessarily. If the reflected API exposes the queue parameter then the developer has to provide one, and they have to remember to pass in the main queue. Could that instead be inferred?
The fact that there aren't more examples like this (that I found) might imply that it's not something that has to be handled, but is that because we're not providing good APIs? Is this just going to make that worse, or are we hoping that as more people adopt Swift-native APIs using structured concurrency features things like this will just end up working out?
Or am I just completely off in my understanding of best practices for async APIs with GCD?