1

Compiling in XCode 3.1.1 for OSX 10.5.8 target, 32-bit and i386 build.

I have a modal run loop, running in NSWindow wloop and NSView vloop. The modal loop is started first. It starts, runs and stops as expected. Here's the start:

[NSApp runModalForWindow: wloop];

Then, when the user presses the left mouse button, I do this:

if (ticking == 0) // ticking is set to zero in its definition, so starts that way
{
    ticking = 1; // don't want to do this more than once per loop
    tickCounter = 0;
    cuckCoo = [NSTimer scheduledTimerWithTimeInterval: 1.0f / 10.0f         // 10x per second
                                               target: self                 // method is here in masterView
                                             selector: @selector(onTick:)   // method
                                             userInfo: nil                  // not used
                                              repeats: YES];                // should repeat
}

Checking the return of the call, I do get a timer object, and can confirm that the timer call is made when I expect it to be.

Now, according to the docs, the resulting NSTimer, stored globally as "cuckCoo", should be added to the current run loop automagically. The current run loop is definitely the modal one - at this time other windows are locked out and only the window with the intended mouse action is taking messages.

The method that this calls, "onTick", is very simple (because I can't get it to fire), located in the vloop NSView code, which is where all of this is going on:

- (void) onTick:(NSTimer*)theTimer
{
    tickCounter += 1;
    NSLog(@"Timer started");
}

Then when it's time to stop the modal loop (which works fine, btw), I do this:

[cuckCoo invalidate];
[NSApp stop: nil];
ticking=0;
cuckCoo = NULL;
NSLog(@"tickCounter=%ld",tickCounter);

ticking and tickCounter are both global longs.

I don't get the NSLog message from within onTick, and tickCounter remains at zero as reported by the NSLog at the close of the runloop.

All this compiles and runs fine. I just never get any ticks. I'm at a complete loss. Any ideas, anyone?

fyngyrz
  • 2,458
  • 2
  • 36
  • 43

2 Answers2

3

The problem is related to this statement "The current run loop is definitely the modal one". In Cocoa, each thread has at most one runloop, and each runloop can be run in a variety of "modes". Typical modes are default, event tracking, and modal. Default is the mode the loop normally runs in, while event tracking is typically used to track a drag session of the mouse, and modal is used for things like modal panels.

When you invoke -[NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:] it does schedule the timer immediately, but it only schedules it for the default runloop mode, not the modal runloop mode. The idea behind this is that the app generally shouldn't continue to run behind a modal panel.

To create a timer that fires during a modal runloop, you can use -[NSTimer initWithFireDate:interval:target:selector:userInfo:repeats:] and then -[NSRunLoop addTimer:forMode:].

Jon Hess
  • 14,237
  • 1
  • 47
  • 51
  • 1
    Wow. Thanks for your reply. But what a mess. So I get the current run loop, get the current run mode from that (I have no idea what it is, better ask), get date (now), instantiate time with class method NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:, modify it with the object method -timer initWithFireDate:interval:target:selector:userInfo:repeats:, then currentRunLoop addTimer:forMode:, is that it? I sure hope not, because it crashes in the addTimer, and it's horrifyingly indirect and complex. I just want to create a timer that ticks. It seems... like it should be easier. – fyngyrz Mar 22 '11 at 19:31
  • ok, I got it. Jon's suggestion doesn't work at all. But using JUST +NSTimer timerWithTimeInterval: target: selector: userInfo: repeats: does, **if** I don't use the return from - NSRunLoop currentMode, but instead fill the addTimer: timer forMode: field with NSModalPanelRunLoopMode. It also obviates the need for the hoop jumping with the date. And I should point that that this esoteric sequence isn't easy to find, presuming its documented at all, so hopefully Google takes note. – fyngyrz Mar 22 '11 at 19:57
  • Didn't you end up doing exactly what I suggested? You created the timer, and then added it with the proper mode. – Jon Hess Mar 23 '11 at 00:54
  • Only partially. You said use -[NSTimer initWithFireDate: interval: target: selector: userInfo: repeats:], an object method, requiring the allocation of the timer first, and then the application of the object method, as well as the superfluous date. I did try that. It crashed. So instead, I went with the class method as described, so that the timer was created in the proper mode, and that worked. You had the right basic idea, all right -- the modal runloop is brain-dead to normal requests for timers -- but the wrong level of method to approach it (object instead of class.) Good pointer, though! – fyngyrz Mar 23 '11 at 01:30
  • I think I pin pointed your problem - the existence of run loop modes. It was up to you to take that knowledge and apply it to exactly how your application worked. That you preferred a method that used an interval instead of a date doesn't seem relevant to the correctness of the answer. – Jon Hess Mar 23 '11 at 01:40
  • No, Jon, the problem was that what you suggested crashed. All other merits aside. Your approach just plain didn't work. What I posted does work. That's why I posted it -- because that portion of your answer leads down the wrong path, though for the right reasons -- runloopmodes. As for what I needed to do, I did it and returned to share the benefits, so I don't see the relevance. – fyngyrz Mar 25 '11 at 22:08
1

The answer specific to...

[NSApp runModalForWindow: wloop];

...is, after the modal run loop has been entered:

NSRunLoop *crl;

    cuckCoo = [NSTimer timerWithTimeInterval: 1.0 / 10
                                      target: self
                                    selector: @selector(onTick:)
                                    userInfo: nil
                                     repeats:YES];
    crl = [NSRunLoop currentRunLoop];
    [crl addTimer: cuckCoo forMode: NSModalPanelRunLoopMode];

(crl obtained separately for clarity) Where the onTick method has the form:

- (void) onTick:(NSTimer*)theTimer
{
    // do something tick-tocky
}
fyngyrz
  • 2,458
  • 2
  • 36
  • 43