After some digging, I can tell you that it is not possible to just override the sendMessage method. The framework is intended to offer a cross-platform way to get the system's event queue and then build an event loop upon it. So we must use the nextEventMatchingMask to achieve that.
To clarify here, the issue here isn't actually about restricting yourself to just overriding sendMessage, it's about thinking through how you hook yourself into AppKit. More broadly, there is a significant difference between:
-
Overriding functions to insert functionality into the existing implementation while calling "super" so that the existing implementation still executes.
-
Reimplementing functionality such that you entirely replace system functionality.
Next, some qualifications and comments:
If we just override sendEvent, then the framework user could not build an event loop, because once the framework calls NSApp.run then the main thread would block and the user looses control over the event loop.
What do you actually need to control in your event loop? There's a structural design choice here between:
-
The app directly implements it's own loop.
-
The framework implements a basic event loop "engine", then provides hooks that allow alternations to the frameworks event loop engine.
Fundamentally, AppKit is very strongly built around #2, as are most of our frameworks. The problem you're going to have is that it's VERY difficult to create something that works like #1 if you're building on #2.
This may be a bigger change than you're will to consider, but my own instinct would be build your framework around #2. For platform that are built on #1, that choice doesn't matter. You fit there event loop APIs into your event "engine" and everything works fine. However, for platforms (like AppKit) which are also built around #2, you can then work on adapting their engine into yours, instead of just getting "stuck" trying to force their API toward #1.
The other reason I suggest this approach is that, in practice, there aren't actually that many advantages to #1. In my experience, app event loops written with category #1 APIs fall into one of two cases:
-
The app uses a "standard" pattern that's common to most apps using that platform, which basically means they're actually using a variant of category #2.
-
The app has done something crazy/janky/broken which ends up being a giant unnecessary headache.
Focusing on #2, as far as I can tell, you could basically do nearly anything you want with the event loop cycle by overriding nextEventMatchingMask and sendMessage. Overriding those 2 method lets you:
-
Break out of event waiting at any time by modifying the expiration time passed into nextEventMatchingMask.
-
Modify (or drop) the raw event returned by [super nextEventMatchingMask] by changing it's value before returning to run.
-
View/modify the event before processing by examining the event in sendMessage before calling [super sendEvent].
-
Perform work before/after event processing be executing code before/after nextEventMatchingMask and sendMessage.
That description is a bit abstract, so let my try a pseudo code representation of this. Here's what this would look like "inside" our run implementation:
- (void)run {
<does initial configuration stuff>
while(true) {
[self Your_NextEventMatchingMask]{
Point 1->
[super nextEventMatchingMask]
Point 2->
}
<does stuff with the event>
[self Your_sendEvent]{
Point 3->
[super sendEvent]
Point 4->
}
<does stuff after the event>
}
}
The four points above give you pretty complete control over the entire event processing cycle. There are some oddities of the our implementation that mean it isn't QUITE as flexible as simply overriding "run". Notably (and you could figure this out through your own testing), run doesn't actually use the immediate value returned by nextEventMatchingMask, but actually uses a private variable that's set inside nextEventMatchingMask. That means you can't insert event by simply returning a different event from nextEventMatchingMask.
If you specifically want to create/destroy (not just modify or respond) events, then you can do so by:
-
Discard events by having Your_NextEventMatchingMask call nextEventMatchingMask again instead of returning.
-
Event should normally be inserted using postEvent(_:atStart:), which would route them through the standard event system. Note that this is what I'd recommend doing even if you were fully replacing run.
-
If you specifically need to insert an event "before" another event, then you can push your event into sendEvent "manually".
The other issue here is that there maybe some odd calling patterns here beyond the simple case above. For example:
-
The methods above could either be directly re-entered.
-
The processing of one method calling into the other (particularly, a call to Your_NextEventMatchingMask occurring "inside" the internal processing of Your_sendEvent).
-
Either of those being called off the main thread.
There may be cases where you want more complicated processing, but I think you could handle all of those cases by adding the right set of checks and then calling super then you hit a check.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware