NSTimer Thrashing - Do I Need to Handle It? - ios

It's possible this question is already out there, but I couldn't find it. My question is essentially this. If I have a repeating NSTimer that executes something that takes longer than the timer interval, will there be some thrashing that will crash the app? Alternatively, does the new time event not start until the task being executed completes?

Since the NSTimer runs on the run loop it was created in, I think it can't ever re-enter the method it calls. This document on the run loops confirms this (see the "Timer Sources" section:
"Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine"

You can always just schedule an nstimer that only occurs once and then reschedule it when the function completes.
- (void)myFunction {
......stuff that your method does
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(myFunction) userInfo:nil repeats:NO];
}

A repeating timer always schedules itself based on the scheduled firing time, as opposed to the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.
https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html

As long as you avoid kicking off some asynchronous jobs, you'll be fine. If asynchronously dispatching tasks that routinely take longer than the interval between invocations of the timer, then that queue can get backed up. If doing animations, the timer will fire even though the animation may not be done.
Let me provide two examples. For both examples, let's imagine that we create a timer that fires once per second:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(handleTimer:)
userInfo:#"tick"
repeats:YES];
First example: Let's assume we have some serial queue:
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
Furthermore, let's assume we have a NSTimer handler that does something like:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
[self.queue addOperationWithBlock:^{
NSLog(#"%s starting some slow process; has %d operations queued", __FUNCTION__, self.queue.operationCount);
// to simulate a slow process, let's just sleep for 10 seconds
sleep(10);
NSLog(#"%s done", __FUNCTION__);
}];
}
Because the timer is firing every second, and because the timer handler returns almost immediately (because all it's doing is queueing up background operations), by the time the first queued operation (which takes 10 seconds) finishes and the second one starts, there are already 10 operations sitting on that background queue. And by the time the second background operation finishes, when the third operation kicks off, there are 19 operations queued up. It only gets worse because the NSTimer handler will simply keep getting called, firing more quickly than the slower background operations are getting cleared out of their queue. Obviously, if the handler did everything synchronously in the current queue, though, everything is fine, and there's no backlogging, no "thrashing" by the NSTimer.
Second example: Another example of this problem is animation. Let's assume that the timer handler method is doing something like the following, that starts a 10 second animation that moves a UIImageView:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
[UIView animateWithDuration:10.0
animations:^{
self.imageView.frame = [self determineNewFrame];
}
completion:nil];
}
This won't work (or more accurately, you'll see the subsequent invocations of the timer call handleTimer even though the previous animation is not done). If you're going to do this, you have to keep track of whether the animation is done. You have to do something like:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
if (!self.animating)
{
NSLog(#"%s initiating another animation", __FUNCTION__);
[UIView animateWithDuration:10.0
animations:^{
self.animating = YES;
self.imageView.frame = [self determineNewFrame];
}
completion:^(BOOL finished){
self.animating = NO;
}];
}
}
You either have to do some state flag (like my boolean animation flag) to prevent additional animations before the first one is done, or just not use recurring timers and simply kick off another timer in the completion block of the UIView animation class method.

Related

Call a method with delay of 15 sec irrespective of app state

I need to call a method in every 15 seconds irrespective of any fact, whether it is on any view controller in foreground, whether it is in background or it is killed, I need to call it at all times.
I know I can do the delay task using NSTimer
NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
selector: #selector(callAfterFifteenSeconds:) userInfo: nil repeats: YES];
But, I wanted to know where to implement it so that it could fulfil my condition. I guess I can use it in App Delegate but I need a guidance for this to implement it correctly.
Calling it in App Delegate class is right place but it will not work for following cases.
It will not work if your app is killed from back ground.
It will not in background mode continuously. OS will stop that process after certain period of time.
-If the app is killed, you cannot do anything.
-When the app is in background, the OS may kill that process after certain time interval (I believe it is 15 seconds).
Though you can register for location changes, while the app is in background. In that case, your app will continue to receive location updates (such as for google maps).
-(void)callAfterFifteenSeconds {
//1.) do your work
//2.) If required, you can also choose to skip the next scheduling.
BOOL shouldSchedule = YES;
if (shouldSchedule) {
//3.)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
[self callAfterFifteenSeconds];
});
}
}
[NSThread sleepForTimeInterval:4.0f];
dispatch_async(dispatch_get_main_queue(),
^{
//write you code if you want fire any method after 4 sec//
}
);

Set a timer on a NSURLSession

I'm using NSURLSession with a rather inconsistent REST API. The API can take between 1-50 seconds to respond for reasons out of my control.
I want to let the user know that on long waits (say over 10 seconds), that the request is still processing. I do NOT want to timeout or terminate any requests, though I know this is possible with NSURLSession. I simply want to provide a "still working" popup (for which I am using TSMessages to create).
How would I go about timing this, particularly as the requests are running on a background thread?
You could use NSTimer.
Creates and returns a new NSTimer object and schedules it on the
current run loop in the default mode. After seconds seconds have
elapsed, the timer fires, sending the message aSelector to target.
// Schedule a timer for 10 seconds and then call the method alertUser:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(alertUser:) userInfo:nil repeats:NO];
- (void)alertUser:(id)sender
{
// Alert the user
}
I would instantiate the NSTimer after you begin the NSURLSessonTask.
E.G.
self.dataTask = [self.session dataTaskWithRequest:theRequest];
[self.dataTask resume];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(alertUser:) userInfo:nil repeats:NO];
Per #Rob's comment,
If you have scheduled a repeating NSTimer you should invalidate it in either
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
OR the completion block of NSURLSessionTask. If the NSTimer does not repeat there is no need to invalidate it as it will invalidate itself automatically. Note, once an NSTimer has been invalidated it cannot be reused.

Appropriate way to call and cancel a selector in Objective-c?

So I have an app that when a user touches a certain object, I kick-off a selector via delay. I am not sure I want or need the delay, but am not sure of best practice, maybe a queue? Anyway, here is what I need, regardless of what I have now.
WHAT I HAVE NOW
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(doSomething) object:self];
[self performSelector:#selector(doSomething) withObject:nil afterDelay:2.0];
When the user touches a certain object I need to kick-off a method, but if he/she touches the object again, I want to not call the method.
Use case #1:
User touches object
User does nothing for 2 seconds
Call selector
Use case #2:
User touches object then
User touches object .5 seconds later (so cancel selector call)
User touches object .3 seconds later (so cancel selector call)
User touches object .9 seconds later (so cancel selector call)
User doesn't touch anything for 2 seconds
Call selector
If feel like performSelector and cancelPrevious are hacky. Should I be using some sort of queue and then clearing out the queue every time the user touches again?
Or should I use a timer and just restart the timer each time the user touches it?
I wrote something quick, hopefully it'll help. Every time start is hit the timer resets
#interface ViewController ()
{
NSTimer *timer;
NSInteger seconds;
}
#end
- (IBAction)start:(id)sender
{
seconds = 5;
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(execute) userInfo:nil repeats:YES];
}
- (void)execute
{
if(seconds > 0) {
NSLog(#"seconds: %li", (long)seconds);
seconds--;
}
else {
NSLog(#"fire");
[timer invalidate];
}
}

iOS reading data from server regularly

I need to create a iOS app where the app has to continuously check for the updates from the server(may be every 30 secs). But only when the app is running on the foreground.
I know this will drain the battery, but this will run on a environment where there's no internet. So we can't use push notifications.
Only option I can think of is sending a request to the server every 30 secs or so and get the updates. What is the best way to do this? Using NSTimer and NSURLConnection or any other better approaches?
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again? Is there a chance that app get killed while its on background?
Thanks
Using NSTimer and NSURLConnection or any other better approaches?
My first thought was also to use NSTimer and NSURLConnection.
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again?
Yes, it will. It doesn't exactly pause, but based on my testing in the simulator, the effect is similar. Let's say the timer is set to go off at 00:00:00, 00:00:30, 00:00:60, ... and you background the app at 00:00:15 and resume it at 00:00:45. The timer that was supposed to fire at 00:00:30 fires immediately when you resume (at 00:00:45), and the next firing (at 00:00:60) and subsequent firings are back on schedule.
Is there a chance that app get killed while its on background?
Yes, there is. But if you start the timer whenever the app launches, this shouldn't be a problem, right?
Your best bet is to setup a separate object that manages these operations on a background thread. Then in your app delegate, when
- (void)applicationWillResignActive:(UIApplication *)application
is called, have this special object stop all of it's synchronizing and clean up anything it needs to.
Then when:
- (void)applicationDidBecomeActive:(UIApplication *)application
gets called as the app gets active again, signal your object to query / poll on its background thread again.
Your custom object could have an interface like this
#interface PollingObject : NSObject
{
NSTimer* _timer;
NSUinteger _interval;
BOOL _cancel;
BOOL _isPolling;
dispatch_queue_t _pollQueue;
}
- (void)startPolling;
- (void)stopPolling;
#end
The implementation can be something like this:
#implementation PollingObject : NSObject
- (id)init
{
if (self = [super init])
{
_interval = 1; // 1 second interval
_cancel = NO; // default to NO
_isPolling = NO; // default to NO
// init your background queue
_pollQueue = dispatch_queue_create("com.yourconame.yourappname.pollQueue", NULL);
}
return self;
}
- (void)heartbeat
{
if (_cancel)
{
// stop the timer
[_timer invalidate];
_isPolling = NO;
return;
}
// Runs the polling method ONCE on a background queue
dispatch_async(_pollQueue, ^{
[self pollingMethod];
});
}
- (void)pollingMethod
{
// Do actual network polling work here...but only run it once. (don't loop)
}
- (void)startPolling
{
_cancel = NO;
if (_isPolling)
{
NSLog(#"Already polling");
return;
}
// schedule the method heartbeat to run every second
_timer = [NSTimer scheduledTimerWithTimeInterval:_interval target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
- (void)stopPolling
{
// we set the flag here and the next second the heartbeat will stop the timer
_cancel = YES;
}
#end
Look at Rocket real-time networking which looks easy to setup through AFNetworking 2.0.
https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide
See the last part of this wiki. I have not used it but it would be something I would try if I had your requirements.

iOS Executing loop until certain condition is met of specified time has passed?

As the title states, i have a while loop that will be executed until certain condition is met, or until 5 seconds have passed.
What is the best way to solve this? I have seen some simple tutorial about NSTimer, but it seems to me that selector that is fired within NSTimer will be executed after time interval specified no matter what. I only need to execute it if condition is not met...
Just create an NSTimer scheduled action store the timer and if you reach your what you wanted to achieve deactivate this timer so that it doesn't trigger the action.
Basically:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(yourAction) userInfo:nil repeats:NO];
some code
//for deactivating the timer
[timer invalidate];
timer = nil;
You could start the NSTimer on the main thread (to ensure above code works) with this:
[self performSelectorOnMainThread:#selector(startTimerMethod) withObject:someOrNoObject waitUntilDone:NO];

Resources