NSTimer periodic task doesn't get called while scrolling - ios

I have an NSTimer
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(periodicTimer)
userInfo:nil
repeats:YES];
which does
- (void)periodicTimer
{
NSLog(#"Bang!");
if (timerStart != nil)
[timerLabel setText:[[NSDate date] timeDifference:timerStart]];
}
The problem is that while scrolling a tableview (or doing other tasks) the label doesn't get updated, furthermore, "Bang!" doesn't appear, so I supposed the method doesn't get called.
My question is how to update the label periodically even when the user is playing around with the app interface.

You'll need to add your timer to the UITrackingRunLoopMode to make sure your timer also fires during scrolling.
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 target:self selector:#selector(myTimerAction:) userInfo:nil repeats:YES];
[runloop addTimer:timer forMode:NSRunLoopCommonModes];
[runloop addTimer:timer forMode:UITrackingRunLoopMode];
From:
https://stackoverflow.com/a/1997018/474896

Not sure about this one, but my first guess would be that the main thread on which the interface is being rendered your timer just doesn't get a chance to do anything while its updating the interface.
You could create a new thread with a new run loop for your timer, but that is a bit of an ugly solution maybe. What functionality in your app are you trying to achieve? Maybe we can advise a better strategy than using a timer.

Related

NSTimer while in background mode is working -- but shouldn't it NOT be working?

I've opted in to background location updates in an App I'm working on.
In my LocationManager class, I've got a method that looks like this:
- (void)beginUpdateTimer
{
[self.updateTimer invalidate];
self.updateTimer = [NSTimer timerWithTimeInterval:ForceUpdateDuration
target:self
selector:#selector(updateWithLastKnownLocation)
userInfo:nil
repeats:NO];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_updateTimer forMode:NSRunLoopCommonModes];
}
The method -updateWithLastKnownLocation potentially calls beginUpdateTimer again.
In testing my app, I've discovered that the timer continues to fire upon the app moving in to the background, so long as I've enabled background location updates. Shouldn't this NOT be happening though? Can I rely on this?
Thanks!
Yes, you can rely on it, provided the timer was running at the time you went into the background.

invalidate NSTimer inside dispatch_async

I am creating a game, and inside the game I need to get a time counter.
I works great but when I scroll the map it stops and then when I finish scrolling it start again. I fix this with dispatch_async. Here is the code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timeNext) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
Timer:
__block NSTimer *timer;
But if the app goes to the background I need to stop this times (and when it get active it starts again).
With this way I can scroll the map and the timer works well I try:
- (void)timeNext {
[timer invalidate];
}
and didin't work. Anyone knows how to stop it? or any other idea to accomplish this task?
Don't create your timer on a global queue's thread and then start a run loop on that thread.
Just create your timer on the main thread (main run loop), and add it to the tracking run loop mode. A timer can be added to multiple run loop modes.
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timeNext) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
If you're using UIScrollView to scroll via your game world, timer will be paused while scroll is tracking touches - you need to setup timer to use it with different mode - UITrackingRunLoopMode, e.g.
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
instead of
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
This will allow your timer to fire events while controls such as scroll is tracking user's touches.
[EDIT]
You create timer e.g. via
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
then add this timer to runloop (basically, you should add it to your app main runloop):
NSTimer* timer = [NSTimer timerWithTimeInterval:1./60. target:self selector:#selector(tick) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
I use NSRunLoopCommonModes instead of UITrackingRunLoopMode, everythings looks fine. The main thread ui operation will not block tick function anymore.
NSTimer* timer = [NSTimer timerWithTimeInterval:1./60. target:self selector:#selector(tick) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

iOS NSTimer not working? [duplicate]

This question already has answers here:
NSTimer timerWithTimeInterval: not working
(4 answers)
Closed 8 years ago.
This is my exact code, and it doesn't seem to be working. Can you tell me what I am doing wrong? Note that refreshTimer was already declared in the private interface.
-(void)viewDidLoad {
refreshTimer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(timerTest) userInfo:nil repeats:YES];
}
-(void)timerTest {
NSLog(#"Timer Worked");
}
Give scheduledTimerWithTimeInterval a try:
NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(myMethod) userInfo:nil repeats:YES];
Quoting: NSTimer timerWithTimeInterval: not working
scheduledTimerWithTimeInterval:invocation:repeats: and scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: create timers that get automatically added to an NSRunLoop, meaning that you don't have to add them yourself. Having them added to an NSRunLoop is what causes them to fire.
There is two-option.
If using a timerWithTimeInterval
use a following like it.
refreshTimer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(timerHandler) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:refreshTimer forMode:NSRunLoopCommonModes];
also mode is two-option. NSDefaultRunLoopMode vs NSRunLoopCommonModes
more Information. refer a this documentation: RunLoopManagement
If using a scheduledTimerWithTimeInterval
use a following like it.
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerHandler) userInfo:nil repeats:YES];
scheduled timers are automatically added to the run loop.
more information. refer a this documentation: Timer Programming Topics
In summary
The "timerWithTimeInterval" you have to remember
to add the timer to the run loop that you want to add on.
The "scheduledTimerWithTimeInterval" default auto creates a timer that runs in
the current loop.

NSTimer not firing the selector

In ios5.0 with ARC, in my rootviewcontroller I call a method in a security manager object that is held by the app delegate. In that method I setup the timer as below
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self
selector:#selector(updateModel:) userInfo:str repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
However, this never fires the selector ie. updateModel: never gets called. What may be wrong? Is there another more efficient way I can do this without using NStimer?
Could also be a threading problem:
if
[NSThread isMainThread]
is false then start the timer like this:
dispatch_async(dispatch_get_main_queue(), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
})
You seem to be a bit mixed up with your timer variable.
You initialize a new timer but you aren't actually using it. Do you want to use the timer you initialized or do you want to you ApplicationDelegate.timer?
Here are the two possible solutions.
Option One (assuming that you have a class instance titled ApplicationDelegate and that it has a timer property):
ApplicationDelegate.timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(updateModel:) userInfo:str repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:ApplicationDelegate.timer forMode:NSRunLoopCommonModes];
Option Two:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(updateModel:) userInfo:str repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
I catch the same issue and I fire timer in main queue to solve it:
[NSURLConnection sendAsynchronousRequest:request queue:_operationQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
[self loopUpUpdateStart];
}];
-(void)loopUpUpdateStart{
dispatch_async(dispatch_get_main_queue(), ^{
_loopTimerForUpRevision =
NSTimer scheduledTimerWithTimeInterval: kNetworkLoopIntervalUpRev
target: self
selector: #selector(myCoolMethod)
userInfo: nil
repeats: YES];
TRACE(#"Start Up updates");
});
}
This line has several problems:
[[NSRunLoop currentRunLoop] addTimer:ApplicationDelegate.timer forMode:NSRunLoopCommonModes];
First, it should not be required at all. -scheduledTimerWithTimeInterval:... already adds the timer to the runloop. You do not need to add it again.
Second, the local variable timer is unrelated to the property ApplicationDelegate.timer (which is presumably nil at this point).
If you're talking to the application delegate so much that you've created something called ApplicationDelegate (a global? a macro?), you're talking to it too much. The application delegate is the delegate for the application; it assists in the application starting and stopping and responding to system events. The application delegate is not a place to store global variables. A timer is definitely not the kind of thing you'd fetch from another object in any case.

The selector of my NSTimer does not run. Why?

My code is:
-(void) timerRun{...}
-(void) createTimer
{
NSTimer *timer;
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerRun)
userInfo:nil
repeats:YES];
}
viewDidLoad
{
[NSThread detachNewThreadSelector:#selector(createTimmer)
toTarget:self withObject:nil];
...
}
When I debug, the method createTimer runs ok, but the method does timerRun not run?
Just creating a timer doesn't start it running. You need to both create it and schedule it.
You're actually going to have to do slightly more work than that if you want it to run on a background thread. NSTimers attach to NSRunloops, which are the Cocoa form of an event loop. Each NSThread inherently has a a run loop but you have to tell it to run explicitly.
A run loop with a timer attached can run itself indefinitely but you probably don't want it to because it won't be managing autorelease pools for you.
So, in summary, you probably want to (i) create the timer; (ii) attach it to that thread's run loop; (iii) enter a loop that creates an autorelease pool, runs the run loop for a bit and then drains the autorelease pool.
Code will probably look like:
// create timer
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerRun)
userInfo:nil
repeats:YES];
// attach the timer to this thread's run loop
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// pump the run loop until someone tells us to stop
while(!someQuitCondition)
{
// create a autorelease pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// allow the run loop to run for, arbitrarily, 2 seconds
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
// drain the pool
[pool drain];
}
// clean up after the timer
[timer invalidate];
You have to schedule a timer for it to run. They get attached to a run loop, which in turn updates the timer as necessary.
You can either change createTimer to
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerRun)
userInfo:nil
repeats:YES];
or add
[[NSRunLoop currentRunLoop] addTimer:timer forModes:NSRunLoopCommonModes];
The method signature that you use in scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: must have an argument for the NSTimer as it passes itself as an argument.
You should change your message signature to:
(void)timerRun:(NSTimer *)timer;
You don't need to do anything with the argument, but it should be there. Also in createTimer the selector will become #selector(timerRun:) as it now accepts an argument:
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerRun:)
userInfo:nil
repeats:YES];

Resources