0

I'm testing with run loops in standard (created by XCode) App. My App has 2 buttons:

  1. Start Loop - starts runloop in some mode (see code below);
  2. Stop Loop - change self.stop flag to stop runloop.

`

- (IBAction)stopLoop:(id)sender
{
    self.stop = YES;
}

- (IBAction)startLoop:(id)sender
{
    self.stop = NO;
    do
    {
        [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
        if (self.stop)
        {
            break;
        }
    } while (YES);
}

`

where:
1. runLoopMode is one of the predefined modes (I try each, default, event tracking, modal, connection).
2. runLoopLimitDate [NSDate distantFuture], or [NSDate distantPast], or close feature.
3. self.stop flag is installed in other method, which called by button.

That's all, my App hasn't any other code.

AFAIU, runloop mode is a set of event sources. So, if I run runloop in some mode, runloop will be proceed those event sources, whose are associated with this mode.
By default Cocoa runs runloop in default mode and all events are proceeds greatly. But when user press startLoop button, my App is freezing:
freezed app.
startLoop method is never break this infinity cycle. Application doesn't send any event to me, therefore UI freezing and user can't press stopLoop button. The same problem if I run Core Foundation counterparts.

But, when I try to receive events through NSApplication (of NSWindow) method nextEventMatchingMask:untilDate:inMode:dequeue: and pass the same mode, I receive UI events.

- (IBAction)startLoop:(id)sender
{
    self.stop = NO;
    do
    {
        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES];
        if (event == nil)
        {
            break;
        }
        [NSApp sendEvent:event];
        if (self.stop)
        {
            break;
        }
    } while (YES);
}

There are question: "Why if I run default run loop mode, or some other, in this way, I can't receive events?"
Thanks for your advice.

Lexandr
  • 679
  • 1
  • 6
  • 22
  • you may need to clarify what you are exactly doing. What happens for example if the *other method* never sets `self.stop`? – Volker Feb 27 '14 at 12:50
  • you just block the runloop that way, the limit date means until then the runloop is blocked. Your loop is immediately resetting and thus blocking again. The stop button never has a change to send its action. What do you want to achieve? – Volker Feb 27 '14 at 13:42
  • I'm exploring run loops. I'm expect, that starting my runloop I will be able proceed events (like simple application) but stay inside `while` loop until user press `stopLoop` button. Can you write more detail explanation? I don't understand, why runloop that I run doesn't proceed events? I try send different params to `beforeDate:`, but all the have same results. Thanks. – Lexandr Feb 27 '14 at 14:07
  • try `runUntilDate:` with a date like `[NSDate dateWithTimeIntervalSinceNow:1.0];`and it shouldn't block the event processing. – Volker Feb 27 '14 at 14:10
  • I have try `[NSDate dateWithTimeIntervalSinceNow:1.0];`, but nothing. – Lexandr Feb 27 '14 at 14:12
  • Maybe this helps: http://stackoverflow.com/questions/149646/best-way-to-make-nsrunloop-wait-for-a-flag-to-be-set – Volker Feb 27 '14 at 14:14

3 Answers3

2

You are assuming that running the NSRunLoop in the NSDefaultRunMode processes user input events such as key presses or mouse clicks. I don't think that's the case.

NSApplication fetches events from the event with nextEventMatchingMask:untilDate:inMode:dequeue:. By running the run loop like that your not really fetching any event of the event queue.

Cloud you try something like this:

- (IBAction)startLoop:(id)sender
{
    do
    {
        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask];
        [NSApp sendEvent:event];

        if (self.stop)
        {
            break;
        }
    } while (YES);
}

(Haven't tested this).

pfandrade
  • 2,359
  • 15
  • 25
  • Yes, thanks, this approach works (also for other modes, it is my bug, that I don't wrote this code into initial question, I will), but I'm interesting in another: why run loop modes don't dispatch 'UI' events to me, when `nextEventMatchingMask:untilDate:inMode:dequeue:` with the same modes does? – Lexandr Feb 28 '14 at 09:26
  • 1
    Running an NSRunLoop by itself doesn't fetch any events from the application's event queue (see [Event Dispatch](https://developer.apple.com/librarY/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW4). You should look at `nextEventMatchingMask:untilDate:inMode:dequeue:` as a higher level API than NSRunLoop. – pfandrade Feb 28 '14 at 12:09
0

What happens if you substitute the following code in your app in place of the -startLoop: method?

- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
    NSLog (@"didRun? %@, runMode: %@, dateNow: %@, limitDate: %@",
        didRun ? @"YES" : @"NO",
        runLoopMode,
        [NSDate date],
        limitDate);
}

- (IBAction)startLoop:(id)sender
{
    BOOL didRun = NO;
    do
    {
        didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
        [self debugLogRunLoopInfo:didRun];
        if (self.stop)
        {
            break;
        }
    } while (YES);
}
Bill Garrison
  • 2,039
  • 15
  • 18
  • didRun = YES; currentMode = nil; limitDate = 2014-02-27 02:01:54 после полудня +0000 (I run my app in 16:03) – Lexandr Feb 27 '14 at 14:04
  • 1
    Go with Volker's recommendation above: -runUntilDate: will do what you're looking to do. – Bill Garrison Feb 27 '14 at 14:19
  • I fixed an embarrassing bug in my suggested code above. You can try my code again, but ultimately, you're going to want to use `-runUntilDate:` method instead of `-runMode:beforeDate:` – Bill Garrison Feb 27 '14 at 14:27
0

The -[NSRunLoop runUntilDate:] method spins the runloop in NSDefaultRunLoopMode. Since you're wanting to experiment with runloop modes, you could try the code below.

I've implemented a -myRunLoopUntilDate:runMode: method that does what runUntilDate: is documented to do, but allows you to specify a runloop mode.

All of this is compiled in my text editor (i.e. not compiled at all), so caveat emptor.

- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
    NSLog (@"didRun? %@, runMode: %@, dateNow: %@, limitDate: %@",
        didRun ? @"YES" : @"NO",
        runLoopMode,
        [NSDate date],
        limitDate);
}

- (void) myRunLoopUntilDate:(NSDate *)limitDate runMode:(NSString *)runLoopMode
{
    BOOL didRun = NO;
    do {
        didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:limitDate];
        [self debugLogRunLoopInfo:didRun];
    } while (didRun && ([limitDate timeIntervalSinceNow] > 0));
}

- (IBAction)startLoop:(id)sender
{
    BOOL didRun = NO;
    do
    {
        [self myRunLoopUntilDate:runLimitDate runMode:runLoopMode];
        if (self.stop)
        {
            break;
        }
    } while (YES);
}
Bill Garrison
  • 2,039
  • 15
  • 18