/home/embrenneke

A mostly-fresh start

Blindly Dispatching to the Main Queue

Update 9/12/2018 I’m told dispatch_get_specific does not work the way I think it does, and this is not a valid solution. Please do not use this. Leaving up for posterity.

Original: In my current side-project, I’m writing a swift app that makes liberal use of dispatch queues to move work off the main queue to keep the app responsive. This works great, but there are some Apple APIs (like UIKit) that require calls to be made from the Main thread or Main queue (they are distinct things).

I want to be able to dispatch work to the main queue synchronously, without worrying about whether I’m already running on the main queue or not.

Typically people suggest checking NSThread.isMainThread() before calling or dispatching an action, but if the api you’re calling requires the main queue this may not be enough. You can potentially be running on the main thread, but not the main queue.

There used to an API to check your current dispatch queue, but that was deprecated with iOS 6. The current strategy appears to be setting a queue specific variable that can only be read from the main queue. This is analogous to thread specific storage (TSS) in in C++ land.

I’ve wrapped this all in an extension to DispatchQueue in Swift 4.1. Now all I need to do to blindly dispatch synchronous work to the main queue is call DispatchQueue.dispatchMain { // uikit stuff }.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extension DispatchQueue {
    private static let mainQueueKey = DispatchSpecificKey<Int>()
    private static let mainQueueValue = 1234

    public class func initializeSafeDispatchMain() {
        // set a queue-specific variable that can only be read from the main queue
        DispatchQueue.main.setSpecific(key: mainQueueKey, value: mainQueueValue)
    }

    public class func dispatchMain(_ block: () -> Void) {
        // if the queue-specific variable matches, execute the block immediately
        if DispatchQueue.getSpecific(key: mainQueueKey) == mainQueueValue {
            block()
        } else {
            // queue-specific variable was not found, so dispatch to the main queue
            DispatchQueue.main.sync {
                block()
            }
        }
    }
}

The problem with this approach is initializing the queue specific variable. Right now I have it wrapped in a function called initializeSafeDisptchMain() that must be called by the application before the first use of dispatchMain(). I’d love to find a way to run this automatically on app startup without having to manaully call it from application(:didFinishLaunchingWithOptions:), but I’m blanking on it right now. If you can think of a way to do this that isn’t totally gross, please let me know.