I need to bring an iOS application to macOS using Catalyst. This
application contain parts where it waits for a button to be pressed in
the main thread, using
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
in a while loop to allow some dispatching while waiting. I know that
this is not good style, but I need to convert this old source code and
mentioned that when using this part of code under Catalyst, the main
thread will not dispatch. So the button cannot be clicked, and a beach
ball appears after two seconds.
So, my main advice here is to rewrite things so that you stop doing this. It might be possible to get this working (see below) but, in my experience:
-
It's easier to rewrite things to avoid this kind of modal logic than it seems.
-
The modal loop is harder to get working and tends to keep breaking and/or introducing new failures.
SO, let me start here:
I saw a similar construct for native macOS applications:
NSEvent *event= [[NSApplication sharedApplication] nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate dateWithTimeIntervalSinceNow:0.1] inMode:NSDefaultRunLoopMode dequeue:YES];
if (event) {
[[NSApplication sharedApplication] sendEvent:event]; }
but I do not have access to NSEvent and NSApplication in a Catalyst environment.
Making things a bit more specific, the issue is actually that you don't have "nextEventMatchingMask" or any real "access" to the direct event queue. NSApplication expose a "getEvent" API because, to be frank, it's very old and it was considered fairly "standard" for an API to provide direct access to the event queue.
That doesn't mean it was ever a good idea and, in fact, the code above is a terrible idea and always has been. It reenters the event dispatch system and, in the general case, opens the door to all sort of brain twisting bugs. In practice it only works at all because it's only used in case where the interface has been specifically configured in a way prevents most event from "arriving" (so the actual entry points are limited) and the actual call is occurring in "simple" event processing (so AppKit isn't being reentered while it's in the middle of a complex state).
Question: I there any code snippet which I can use to achieve the above? I do not want to completely rewrite old code if there is a solution for this. Any ideas or hints are highly appreciated.
Basically, "no", I don't think there is any way to make this work. The reason this works on iOS:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
...is that iOS's runloop configuration is much simple than macOS. Basically, "everything" is in the default mode, so "everything" gets dispatched when you run in that mode. However, Catalyst is actually using AppKit's runloop system (you can see this if you sample an Catalyst app), which means it ends up following the same rules/behaviors.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware