22

I am wondering if someone can explain why dispatching back to the main queue and creating a repeating NSTimer I am having to add it to RUN LOOP for it too fire? Even when using performselectorOnMainThread I still have to add it to a RUN LOOP to get it to fire.

Below is an example of my question:

#define queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define mainqueue dispatch_get_main_queue()

- (void)someMethodBeginCalled
{
    dispatch_async(queue, ^{
        int x = 0;
        dispatch_async(mainqueue, ^(void){
            if([_delegate respondsToSelector:@selector(complete:)])
                [_delegate complete:nil];
        });
    });
}

- (void)compelete:(id)object
{
    [self startTimer];

    //[self performSelectorOnMainThread:@selector(startTimer) withObject:nil waitUntilDone:NO];
}

- (void)startTimer
{
    NSTimer timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(callsomethingelse) userInfo:nil repeats:YES];

    //NSDefaultRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes];
}

EDIT: I believe I worded this question very poorly. I would like to know why [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes]; is necessary in startTimer if I call someMethodBeginCalled. If I don't include that line, the timer doesn't fire.

If I call startTimer from viewDidLoad for example, I can remove the NSRunLoop line and the timer will fire every 60 seconds.

mfaani
  • 33,269
  • 19
  • 164
  • 293
chicken
  • 1,618
  • 5
  • 24
  • 35

6 Answers6

22

And here's how to add an NSTimer to a runloop:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
  • 2
    The OP is asking the reason to add a timer into run loop, instead of how to add a timer into run loop. – chengsam May 17 '17 at 03:14
19

You could always use this method instead:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(getBusLocation) userInfo:nil repeats:YES];

This will save you a line, as it will add it to the run loop automatically.

borrrden
  • 33,256
  • 8
  • 74
  • 109
  • 6
    This is the normal approach. It is rare to manually add a timer to a runloop. You should generally, however, keep the timer in an ivar so that you can invalidate it when needed. – Rob Napier Mar 29 '12 at 02:42
  • Yes, otherwise the object it calls will never be deallocated if you have a repeating timer. – borrrden Mar 29 '12 at 04:45
  • 1
    I edited the question, basically i am asking why cant i use just your line when i initialize it from dispatch_async on the main queue. Without the NSRunLoop line there the timer wont fire. – chicken Mar 29 '12 at 12:15
  • AH, I got it now. I don't know why that is...I have relatively little experience with GCD ^^; – borrrden Mar 29 '12 at 12:54
  • 1
    @borrrden's line should work if you call it on the main queue with dispatch_async(). Can you post the code you believe is not working? Are you sure you're using `scheduledTimer...` (as borrrden does) rather than just `timer...` (as you did originally)? – Rob Napier Mar 29 '12 at 14:18
  • wow! i didnt even notice i wasnt using "scheduledTimerWithTimeInterval" major idiot moment. Thanks Borrden/Rob. Answer accepted. – chicken Mar 29 '12 at 15:11
  • It should really be `NSTimer *timer`... forgot the asterisk – erdekhayser Sep 28 '13 at 16:51
  • @RobNapier calling scheduledTimer on the main queue with dispatch_async() .so no need the runloop.Working awesome. – Dharma May 10 '18 at 10:16
4

Because, as the docs say:

Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops retain their timers, so you can release a timer after you have added it to a run loop.

It is a design decision that Apple made when they wrote the code for NSTimer (and I'm sure they had good reason to do so) and there is nothing we can do to get around it. Is it really that burdensome?

sosborn
  • 14,676
  • 2
  • 42
  • 46
  • 8
    To be clear: NSTimers are implemented as a part of the run loop. One of the run loop sources is "are there any pending NSTimers that need to be fired." An NSTimer is just some data that hangs on a runloop. This is a very good thing, because it means that timers do not require threads. – Rob Napier Mar 29 '12 at 02:16
3

Like @sosborn said, NSTimers depend on NSRunLoops, and since GCD queues create threads that don't have run loops, NSTimer doesn't play well with GCD.

Check out this other StackOverflow question on the matter: Is it safe to schedule and invalidate NSTimers on a GCD serial queue?

To solve that problem, I implemented MSWeakTimer: https://github.com/mindsnacks/MSWeakTimer (and had the implementation checked by a libdispatch engineer at the last WWDC!)

Community
  • 1
  • 1
Javier Soto
  • 4,840
  • 4
  • 26
  • 46
2

Timer method won't be called since GCD queues create threads that don't have run loops

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
   [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
       NSLog(@"Timer method from GCD main queue");
   }];

});

However when dispatched on main queue the timer method will be called as it will get added to main threads run loop.

dispatch_async(dispatch_get_main_queue(), ^{
   [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
       NSLog(@"Timer method from GCD main queue");
   }];

});
Şafak Gezer
  • 3,928
  • 3
  • 47
  • 49
Say2Manuj
  • 709
  • 10
  • 20
1

Adding the timer to the runloop didn't work in my case. I had to create the timer on the main thread. I was doing this thread creation in a MultipeerConnectivity delegate.

    dispatch_async(dispatch_get_main_queue(), ^{
        self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval  invocation: self.invocation repeats:YES];
    });
Chris Prince
  • 7,288
  • 2
  • 48
  • 66