How to run code that requires a DispatchQueue at the default global queue an `actor` uses?

I'm in the following dilemma. I would like to isolate UI code by the MainActor and all the logic by a custom global LogicActor. That's all fine until I need to add some APIs that require a reference to a DispatchQueue or they will fallback to the main queue.

CBCentralManager is one such example.

I would like to create a serial queue which then would target the same queue that the LogicActor uses by default under the hood. My assumption is that this way the system will know that it's running at the same context and will not create additional context switches.

The problem however is that I cannot find if there is an actual way to do exactly that. :thinking:

I don't think it's possible to do directly right now, because of how the default executor works in swift. According the the WWDC2021 video that goes into detail about the internals, the default executor has nothing to do with GCD and is implemented from scratch with a similar "cooperative thread pool" approach as GCD, but optimized for Swift concurrency. The whole concept of "targeting an executor" concept is thrown away, because Swift's executor is essentially a singleton, where GCD provides a composable abstraction in the form of a queue. Considering that GCD is not extensible (e.g. doesn't allow you to define your own implementation of a queue), you can't even implement a new type of queue that schedules the blocks on the actor's mailbox.

When the "custom executor" proposal is implemented, you'd be able to customize the executor of the LogicActor to use your a dedicated dispatch queue instead of the default executor, which you would then be able to use in GCD-dependent APIs as you've described.

For now, without doing it directly, one way I could think of would be to create an UnsafeSendable class with the queue and wrap it inside an actor, which would forward all its method calls to the underlying class. This way you could use the wrapper actor as a @globalActor and essentially ignore its internal mailbox in favor of your own DispatchQueue-based implementation.
Yes, this looks ugly and tedious, but without custom executors, this is the only way that I can think of how you could accomplish what you want.

2 Likes

I agree with Gor Gyolchanyan’s analysis of the current situation but I want to suggest an alternative…

The way I’d approach this to create an object that implements the CBCentralManagerDelegate protocol and uses AsyncStream to route all the events to an AsyncSequence (the sequence elements being an enum where each case encapsulates a specific event). Your actor could then instantiate that object and use it as the delegate for the CBCentralManager.

I haven’t yet had a chance to try this out for real yet )-:

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes
Terms of Service

Privacy Policy

Cookie Policy