iphone - NSTimers in background - objective-c

Im developing an app that has to run in the background. It's a location based app, so it runs all the time, the OS doesn't kill it.
It should send some info every 10 secs(just for debugging), I set a timer once its in the background. I set a breakpoint in the function that should be executed every 10 secs, which is never called, but if I pause the app and then continue the timer is called, and then the timer is executed every 10 secs without problems, weird right?
I thought that the timer would be executing anyway when I wasn't debugging, but it isn't, same thing as if I didn't pause the debugging.
My question is WHY?? The timer is set correctly(I assume) since it works after pausing, but it's not.
Any ideas?
The way I set the timer is:
self.timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(doStuff) userInfo:nil repeats:YES];
And in the function I connect to a webservice.
Thanks.

I have a similar app design and was stuck on the same thing. What I found somewhere on the internet is adding this type of statement applicationDidEnterBackground:
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
This tells the os that you still have things going and not to stop it.
I have my timer attached to this function
//this is a wrapper method to fit the required selector signature
- (void)timeIntervalEnded:(NSTimer*)timer {
[self writeToLog:[NSString stringWithFormat:#"Timer Ended On %#",[NSDate date]]];
[self startReadingLocation];
[timer invalidate];
timer=nil;
}
I set the timer in my my location manager delegate methods.
I feel your pain. I found that these things were super finicky. This is what worked for me. I hope it helps. I have found that there isn't any really restrictions in what you can do in the background.

There might be restrictions on what you can do in the background. Try adding the timer to the run loop before going into the background. Even that might not work; it may be that the only code of yours that can run in the background is the code called by the Core Location methods you've signed up for (e.g. locationManager:didUpdate...). But my impression is that timers already running before you start to go into the background will continue to run.

Related

How to keep nstimer run in background?

I am using the bellow code but timer kill in background. but i want to keep running my nstimer in background-
NSTimeInterval time = 5;
self.locationUpdateTimer =
[NSTimer scheduledTimerWithTimeInterval:time
target:self
selector:#selector(updateLocation1)
userInfo:nil
repeats:NO];
You can get location events while the app is in the background, and here is Apple documentation that explains how to do that. And you probably also want to use something called "the Significant-Change Location Service".
And when the change happens, you'll get a call on your CoreLocation method "location manager: didUpdateLocations:". Sample code can be seen in the "Significant-Change Location Service" section.
Doing the above will be a lot easier to do than to try to implement a timer that runs while your application is in the background (and probably fully suspended, as many apps tend to be).
And if you didn't get a location update, that means your phone didn't move. :-)

iPad app freezes during NSOperationQueue

I have a problem with an app where I implemented an NSOperationQueue.
It seems that pressing the close button of the iPad makes the UI freeze. The app itself is still running in the background, the UI is updated, it's just that it won't answer to touches or rotation anymore and it doesn't get closed as it should be.
I have an update module that downloads quite a long list of xml files and saves them on the device. The operation queue has a MaxConcurentOperations value of 2.
Usually everything works fine, the app runs fine and dandy, responding to my touches and rotation UNTIL I press the device's button. After this, the UI simply freezes. The progress is still updated (an UILabel), recurrent animations are still displayed, but the app is not closed until all the operations are done.
I'm not calling waitUntilAllOperationsAreFinished on my queue so I don't know what might be causing this.. So far, I've only made tests on a first generation iPad, with iOs 5.0
If anyone can provide me with some tips, I'll really appreciate it. If necessary, I can post the NSOperationQueue and NSOperation class codes, but somehow I have the feeling this about me approaching this wrongly, and not about a faulty line of code
[edit]
I also use a timer to periodically check on the download status, but I noticed that not calling the timer doesn't eliminate the problem
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(xmlDownloaded) userInfo:nil repeats:YES];
[edit2]
After some further research, I made sure that my operations are concurrent and just to be sure, I changed the way I added my operations to the queue.
Instead of
[downloadQueue addOperation:op];
I added them to a mutable array called "operations" and in the end I used
[downloadQueue addOperations:operations waitUntilFinished:NO];
but my app still freezes when I press the close button...
Wild guess, you are locking the main thread waiting for your operation to complete / you are destroying your operation delegate on viewWillDisappear?
It seems there was another function causing this, even though I don't understand why...
I had a label animation
CABasicAnimation *fadey = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadey setToValue:[NSNumber numberWithFloat:0.35f]];
fadey.repeatCount = HUGE_VALF;
[fadey setAutoreverses:YES];
[fadey setDuration:0.6f];
This small function was causing the app to wait until all the operations were finished in order to close the app. The weird part is that the function was not even called in with the NSOperations, but before them
Again, I have no idea why... everything breaks when I press the close button, otherwise there are no problem. So if anyone else runs into a similar issue, it might help to check for some repeating animations

Run repeating timer when applicationDidEnterBackground

My goal is to run a repeating timer every five seconds if and only if the application is running in the background. I've tried a couple of ideas, but they don't seem to work.
Idea 1: Doesn't run even once.
- (void)applicationDidEnterBackground:(UIApplication *)application {
[NSTimer scheduledTimerWithTimeInterval:(5.0/5.0) target:self selector:#selector(check_expiry) userInfo:nil repeats:YES];
}
Idea 2: Runs every five seconds, but I can't seem to stop the loop.
- (void)applicationDidEnterBackground:(UIApplication *)application {
counter = YES;
while (counter) {
sleep(5);
[self check_expiry];
}
// Counter is set to NO in willEnterForeground and didBecomeActive, but this loop continues to run due the sleep();
}
How can I get this loop to run properly?
Thanks!
When an application "enters the background" in iOS, that's not like normal operating systems, where it continues to run. The application enters a suspended state. It doesn't keep running; only application state is preserved, and even that's not guaranteed - if the device is running low on memory, iOS will happily terminate your application to give the memory to the active application. If you attempt to block in applicationDidEnterBackground:, like you are doing with sleep(), iOS will simply terminate your application for not returning from that method promptly.
Your application is woken up periodically if it's configured for background processing GPS events, VOIP, etc., but abusing those just to get your app to run will stop you from getting App Store approval.
This is all covered in The iOS Application Programming Guide.
For anyone looking for a workaround, I merely created a system that schedules timers at a later date when the applicationDidEnterBackground: and changed/cancelled them when they were edited/deleted. Information on timer scheduling was stored in a local dictionary.

NSTimer: Getting firing to NOT act retroactively

I'm currently using the snippet of code presented below to fire some methods every second. My app is running in the background. The problem is that if the computer wakes up after a sleep period the timer wants to retroactively fire all the methods it has missed. Similar issues come up if the user were to change the System Clock time.
Basically I want to implement the proper timer method that will have my methods called only every current second. If a second (or minute or hour or day) has passed and for whatever reason the methods weren't called I want my app to just continue from the current moment in time.
Also, can we keep this while using NSTimer?
Thanks!
-(void)start
{
NSTimer * timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(tasks:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode];
}
To handle the big time changes you can use the link UIApplicationSignificantTimeChangeNotification and unregister/reregister your timer.
To deal with the sleep issue, you can unregister and then reregister your timer whenever the machine goes to sleep and wakes up. See this technical note for information on how to do that. This solution won't work for changing the system time, though.

Best way to make NSRunLoop wait for a flag to be set?

In the Apple documentation for NSRunLoop there is sample code demonstrating suspending execution while waiting for a flag to be set by something else.
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
I have been using this and it works but in investigating a performance issue I tracked it down to this piece of code. I use almost exactly the same piece of code (just the name of the flag is different :) and if I put a NSLog on the line after the flag is being set (in another method) and then a line after the while() there is a seemingly random wait between the two log statements of several seconds.
The delay does not seem to be different on slower or faster machines but does vary from run to run being at least a couple of seconds and up to 10 seconds.
I have worked around this issue with the following code but it does not seem right that the original code doesn't work.
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
using this code, the log statements when setting the flag and after the while loop are now consistently less than 0.1 seconds apart.
Anyone any ideas why the original code exhibits this behaviour?
Runloops can be a bit of a magic box where stuff just happens.
Basically you're telling the runloop to go process some events and then return. OR return if it doesn't process any events before the timeout is hit.
With 0.1 second timeout, you're htting the timeout more often than not. The runloop fires, doesn't process any events and returns in 0.1 of second. Occasionally it'll get a chance to process an event.
With your distantFuture timeout, the runloop will wait foreever until it processes an event. So when it returns to you, it has just processed an event of some kind.
A short timeout value will consume considerably more CPU than the infinite timeout but there are good reasons for using a short timeout, for example if you want to terminate the process/thread the runloop is running in. You'll probably want the runloop to notice that a flag has changed and that it needs to bail out ASAP.
You might want to play around with runloop observers so you can see exactly what the runloop is doing.
See this Apple doc for more information.
Okay, I explained you the problem, here's a possible solution:
#implementation MyWindowController
volatile BOOL pageStillLoading;
- (void) runInBackground:(id)arg
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Simmulate web page loading
sleep(5);
// This will not wake up the runloop on main thread!
pageStillLoading = NO;
// Wake up the main thread from the runloop
[self performSelectorOnMainThread:#selector(wakeUpMainThreadRunloop:) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void) wakeUpMainThreadRunloop:(id)arg
{
// This method is executed on main thread!
// It doesn't need to do anything actually, just having it run will
// make sure the main thread stops running the runloop
}
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:#selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
while (pageStillLoading) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[progress setHidden:YES];
}
#end
start displays a progress indicator and captures the main thread in an internal runloop. It will stay there till the other thread announces that it is done. To wake up the main thread, it will make it process a function with no purpose other than waking the main thread up.
This is just one way how you can do it. A notification being posted and processed on main thread might be preferable (also other threads could register for it), but the solution above is the simplest I can think of. BTW it is not really thread-safe. To really be thread-safe, every access to the boolean needs to be locked by a NSLock object from either thread (using such a lock also makes "volatile" obsolete, as variables protected by a lock are implicit volatile according to POSIX standard; the C standard however doesn't know about locks, so here only volatile can guarantee this code to work; GCC doesn't need volatile to be set for a variable protected by locks).
In general, if you are processing events yourself in a loop, you're Doing It Wrong. It can cause a ton of messy problems, in my experience.
If you want to run modally -- for example, showing a progress panel -- run modally! Go ahead and use the NSApplication methods, run modally for the progress sheet, then stop the modal when the load is done. See the Apple documentation, for example http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html .
If you just want a view to be up for the duration of your load, but you don't want it to be modal (eg, you want other views to be able to respond to events), then you should do something much simpler. For instance, you could do this:
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:#selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
}
- (void)wakeUpMainThreadRunloop:(id)arg
{
[progress setHidden:YES];
}
And you're done. No need to keep control of the run loop!
-Wil
If you want to be able to set your flag variable and have the run loop immediately notice, just use -[NSRunLoop performSelector:target:argument:order:modes: to ask the run loop to invoke the method that sets the flag to false. This will cause your run loop to spin immediately, the method to be invoked, and then the flag will be checked.
At your code the current thread will check for the variable to have changed every 0.1 seconds. In the Apple code example, changing the variable will not have any effect. The runloop will run till it processes some event. If the value of webViewIsLoading has changed, no event is generated automatically, thus it will stay in the loop, why would it break out of it? It will stay there, till it gets some other event to process, then it will break out of it. This may happen in 1, 3, 5, 10 or even 20 seconds. And until that happens, it will not break out of the runloop and thus it won't notice that this variable has changed. IOW the Apple code you quoted is indeterministic. This example will only work if the value change of webViewIsLoading also creates an event that causes the runloop to wake up and this seems not to be the case (or at least not always).
I think you should re-think the problem. Since your variable is named webViewIsLoading, do you wait for a webpage to be loaded? Are you using Webkit for that? I doubt you need such a variable at all, nor any of the code you have posted. Instead you should code your app asynchronously. You should start the "web page load process" and then go back to the main loop and as soon as the page finished loading, you should asynchronously post a notification that is processed within the main thread and runs the code that should run as soon as loading has finished.
I’ve had similar issues while trying to manage NSRunLoops. The discussion for runMode:beforeDate: on the class references page says:
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. Mac OS X may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
My best guess is that an input source is attached to your NSRunLoop, perhaps by OS X itself, and that runMode:beforeDate: is blocking until that input source either has some input processed, or is removed. In your case it was taking "couple of seconds and up to 10 seconds" for this to happen, at which point runMode:beforeDate: would return with a boolean, the while() would run again, it would detect that shouldKeepRunning has been set to NO, and the loop would terminate.
With your refinement the runMode:beforeDate: will return within 0.1 seconds, regardless of whether or not it has attached input sources or has processed any input. It's an educated guess (I'm not an expert on the run loop internals), but think your refinement is the right way to handle the situation.
Your second example just work around as you poll to check input of the run loop within time interval 0.1.
Occasionally I find a solution for your first example:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]);