Annotating this thread subsequent to the transition to Apple Silicon, which is basically complete at the time of this writing. I think the methodology proposed at the top of this discussion is a workable and effective strategy for dealing with this problem which is going to become more and more pervasive. Many new SDKs from Apple will be thread-safe and capable of generating KVO notifications on any thread or queue. However, I think it unlikely at AppKit and UIKit will be thread safe. And there's the challenge of supporting the widest array of Mac hardware.
The Objective-C runtime already has solutions for this problem which date back to a technology called Distributed Objects. Not much has been said about this tech for a long while because security concerns promoted XPC to the foreground but XPC doesn't really provide the same functionality.
The point here is that the NSProxy NSInvocation classes and patterns can be used to "remote" almost any Objective-C method call, including over thread boundaries, process boundaries and to remote machines on the LAN or interweb. Check out NSDistantObject for some perspective.
You can build a controller layer whose sole purpose is to proxy all KVO notifications onto the main thread to AppKit.
Take this sample code from the archive for example:
https://developer.apple.com/library/archive/samplecode/AVRecorder/Introduction/Intro.html#//apple_ref/doc/uid/DTS40011004
I have refactored and referred to this sample several times; it's an excellent sample. But as of 2023, the KVO bindings from the UI are not properly working. Exceptions are being thrown and KVO notifications lost, leading to indeterminate states of the UI. Maybe these are bugs in AppKit that will be remedied (sometime in the future).
However, I was easily able to solve the problems with this sample by building a controller layer between AppKit and AVCaptureDevice et al. This was before I found NSInvocation and basically I am dispatching to the main thread. My solution is just a simple proxy object that forwards all the valueForKeyPath type methods to the target objects (I have one controller bound to all the various AVCaptureDevice type key paths). It's a very simple class and has restored this sample code to its original lustre and glory. But it could be even simpler next time I revisit the code:
For my next Cocoa nightmare I dove deeper into NSInvocation and learned that you can completely remote an entire class with just four Objective-C methods. Check out the docs for methodSignatureForSelector: and go down the rabbit hole:
from NSInvocation:
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
- (void)invokeWithTarget:(id)target;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (id)forwardingTargetForSelector:(SEL)methodSelector;`
You'll get warnings from a modern Objective-C compiler so declare the exposed keypaths/properties as usual and mark them as @dynamic so the compiler doesn't synthesize methods for them. Once you get to googling NSInvocation and any of the four methods listed above, I think you'll find much has been written on this subject going back to Panther and even OpenStep.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/DistrObjects/DistrObjects.html