44

I want to be able to schedule three small events in the future without having to write a function for each. How can I do this using NSTimer? I understand blocks facilitate anonymous functions but can they be used within NSTimer and if so, how?

[NSTimer scheduledTimerWithTimeInterval:gameInterval  
         target:self selector:@selector(/* I simply want to update a label here */) 
         userInfo:nil repeats:NO];
Chris
  • 26,744
  • 48
  • 193
  • 345
  • 4
    Why don't you use `dispatch_after()` ? That is a GCD function and takes a block as parameter. – Martin R Feb 17 '13 at 19:10
  • Never heard of it... How do I use that? As long as I can say "wait X seconds a then do this" I'm happy! – Chris Feb 17 '13 at 19:11
  • 1
    what about the `–performSelector:withObject:afterDelay:` method? – holex Feb 17 '13 at 19:23
  • You can try to look here (NSTimer + Blocks) https://github.com/pandamonia/BlocksKit/blob/master/BlocksKit/NSTimer%2BBlocksKit.h Or, in general here: https://github.com/pandamonia/BlocksKit – tt.Kilew Feb 17 '13 at 19:13
  • @MartinR not all instances of NSTimer warrant the use of dispatch_after. For example, if you wanted to specifically be able to stop a timed event from occurring it wouldn't always be the equivalent to set a flag in the block of your dispatch. If I had an async function that fires on a user interaction and I wanted to wait until they finished interacting in order to fire the payload, with dispatch_after it would fire once for every interaction where as the timer could be consistently invalidated and would never even attempt to fire... saving much memory. – GoreDefex Aug 08 '17 at 19:02
  • for anyone googling here, the syntax/paradigm has changed drastically over the years. the latest one is bizarrely sort of undocumented, see down the bottom! – Fattie Dec 21 '18 at 20:54
  • To answer the question from 7 years ago, you use dispatch_after() like so: `dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((__delay) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{/*Your Code Here*/});` Just replace `__delay` with a number, in seconds, and then put the code you want to run inside `/*Your Code Here*/` – Albert Renshaw Aug 11 '20 at 07:12

9 Answers9

57

You can make use of dispatch_after if you want to achieve something similar to NSTimer and block execution.

Here is the sample code for the same:

    int64_t delayInSeconds = gameInterval; // Your Game Interval as mentioned above by you

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

        // Update your label here. 

    });

Hope this helps.

Reno Jones
  • 1,979
  • 1
  • 18
  • 30
  • 9
    Note! You've used an int64_t for your delay in seconds, so this will only work for whole numbers of seconds! gameInterval, I'm guessing, is less than 1 second, so this will not do what you want. (The Xcode template for this is quite deceptive.) – Jesse Rusak Feb 17 '13 at 19:16
  • 1
    Jesse is right. Note that the actual parameter accepts a value measured in *nanoseconds* though, hence the `NSEC_PER_SEC` constant. So you can certainly specify fractions of a second, but you do have to be careful with your numeric types. Something like `(int64_t)(gameInterval * (double)NSEC_PER_SEC)` where `gameInterval` is a `double` should work. – devios1 Dec 10 '14 at 21:58
  • 1
    The post asks how to do this with NSTimer. I can't invalidate using dispatch_after. – circuitry Aug 23 '16 at 01:30
30

You can actually call:

NSTimer.scheduledTimerWithTimeInterval(ti: NSTimeInterval,
                    target: AnyObject, 
                    selector: #Selector, 
                    userInfo: AnyObject?, 
                    repeats: Bool)

Use it like this:

NSTimer.scheduledTimerWithTimeInterval(1, 
                    target: NSBlockOperation(block: {...}), 
                    selector: #selector(NSOperation.main), 
                    userInfo: nil, 
                    repeats: true)
Peter Peng
  • 1,910
  • 1
  • 27
  • 37
19

A block based timer API exists in Cocoa (as of iOS 10+ / macOS 10.12+) – here's how you can use it from Swift 3 onwards:

Timer(timeInterval: gameInterval, repeats: false) { _ in
    print("herp derp")
}

… or in Objective-C:

[NSTimer scheduledTimerWithTimeInterval:gameInterval repeats:NO block:^(NSTimer *timer) {
    NSLog(@"herp derp");
}];

As noted in a comment, be careful not to use strong self references inside the block to avoid retain cycles (for more info).

If you need to target OS versions older than iOS10, macOS 12, tvOS 10, watchOS 3, you should use one of the other solutions.

mz2
  • 4,672
  • 1
  • 27
  • 47
  • 1
    I was delighted to discover this addition recently. Given that we've had blocks since iOS 4, it's kind of surprising that it took Apple until iOS 10 to throw in a block-based API for NSTimer! – Reid Nov 17 '16 at 17:53
  • 1
    Surprisingly this doesn't work for me, at least not in Objective-C. The block simply never gets called. It's not a problem with the timeout because the regular NSTimer initialization works with the same timeout. It's not a problem with holding the NSTimer variable either, because it doesn't work even if I strongly hold it. – Gobe Dec 20 '16 at 00:56
  • I assure you that Cocoa API works. You're probably calling it outside the main thread (or another thread that has a suitably configured run loop on it). – mz2 Dec 20 '16 at 01:03
  • 1
    I'm so glad that I'm not the only one who logs "herp derp" to test things. – Paludis Jan 02 '17 at 23:00
  • Actually, in Swift you need to use the "withTimeInterval" form, not "timeInterval". I've tried both side by side and it baffled me until I figured it out. Both will compile, only one will work: let _ = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in print("herp derp2") } – JohnnyC Apr 01 '18 at 17:07
  • @JohnnyC , that is the strangest thing I've seen all yeat, and it is December 21! thanks!!! – Fattie Dec 21 '18 at 20:49
  • 1
    Make sure to only use weak references inside of this or you'll have a retain cycle. – Albert Renshaw Aug 11 '20 at 06:16
  • @AlbertRenshaw thanks for that, I updated the answer pointing out this danger. – mz2 Aug 11 '20 at 07:28
9

Objective-C version of @Peter Peng's answer:

_actionDelayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Well this is useless.");
}] selector:@selector(main) userInfo:nil repeats:YES];
William Denniss
  • 16,089
  • 7
  • 81
  • 124
6

It's pretty easy, but it isn't included in Apple framework, not yet at least.

You can write a block-based wrapper for NSTimer yourself, e.g. using GCD, or you can use existing 3rd-party libraries like this one: https://github.com/jivadevoe/NSTimer-Blocks.

coverback
  • 4,413
  • 1
  • 19
  • 31
3

I have created a category on NSTimer witch makes it possible to use it with blocks.

https://github.com/mBrissman/NSTimer-Block

mBrissman
  • 136
  • 5
2

As of late 2018, you do it precisely like this:

Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { timer in
  print("no, seriously, this works on iPhone")
} 

This thanks to @JohnnyC !

Truly strange!

enter image description here

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
0

I love this hack @Peter-Pang!! BlockOperation is created on the fly, own by the Timer which itself is own by the running queue, and call the main selector on the block to run it.... nice!!!

Updated for Swift 3

Timer.scheduledTimer(timeInterval: 1, target: BlockOperation { // ... }, selector: #selector(Operation.main), userInfo: nil, repeats: false)

Hugues BR
  • 2,238
  • 1
  • 20
  • 26
0

I've made a Macro that will do this and auto handle dealloc, arc, retain cycles etc. No need to worry about using weak references. It will also always be on main thread

#define inlineTimer(__interval, __block) {\
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.0) * NSEC_PER_SEC)), dispatch_get_main_queue(), (__block));\
[NSTimer scheduledTimerWithTimeInterval:__interval repeats:YES block:^(NSTimer *__timer) {\
if (self.window != nil) {\
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.0) * NSEC_PER_SEC)), dispatch_get_main_queue(), (__block));\
} else {\
[__timer invalidate];\
}\
}];\
}

Example usage:

__block ticks = 0;

inlineTimer(0.5, ^{
    ticks++;
    self.label.text = [NSString stringWithFormat:@"Ticks: %i", ticks];//strong reference to self and self.label won't cause retain cycle! Wahoo
});

Note that self.label is not a weak reference, yet this will all be automatically released due to how the macro is structured.

Naturally this only works w/ UI classes, given that it's checking .window for when it should dealloc.

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195