Background task block function not finishing - iphone

I'm working on an iphone app that occasionally fires a task in the background to rearrange some data and upload it to a server. I've used a lot of the principles from Grand Central Dispatch (GCD) with CoreData to get things running, since I'm editing objects that persist in Core Data, but the code only occasionally finishes running despite the application saying it has almost the full 600 seconds of execution time remaining.
The code i'm using:
__block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance
NSLog(#"BackgroundTimeRemaining before block: %f", application.backgroundTimeRemaining);
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you.
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
NSLog(#"BackgroundTimeRemaining after block: %f", application.backgroundTimeRemaining);
NSLog(#"Fixing item in the background");
//Create secondary managed object context for new thread
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
[backgroundContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
/* Save the background context and handle the save notification */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
//creating runloop to kill location manager when done
NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:60];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate];
NSLog(#"Stop time = %#", stopDate);
MasterViewController *masterViewContoller = [[MasterViewController alloc] init];
masterViewContoller.managedObjectContext = backgroundContext;
[[masterViewContoller locationManager] startUpdatingLocation];
NSLog(#"Successfully fired up masterViewController class");
[masterViewContoller adjustDataInBackground:FALSE];
NSLog(#"Fixed Object!");
//save background context
[backgroundContext save:NULL];
//unregister self for notifications
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
The issue is that "adjustDataInBackground:FALSE" is a pretty long method that calls additional supporting methods (including creation and saving of core data objects), and when the background task doesn't allow all of those methods to finish it corrupts my data.
Is there a better way of handling this kind of an operation? Do i need to put all my raw code into the background task block directly?

So it turns out I had two weird things going on that were tripping up the background task:
Asynchronous URL connections (when their initiating method finished, iOS thought the background task was done even if the response hadn't yet been received)
A location manager specific to the background task (apparently a major no-no...apple's got some documentation on this but the console would spit out an error about it sometimes)
Here's the code I'm now using (it works so far):
__block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance
NSLog(#"BackgroundTimeRemaining before block: %f", application.backgroundTimeRemaining);
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you.
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
NSLog(#"BackgroundTimeRemaining after block: %f", application.backgroundTimeRemaining);
//Create secondary managed object context for new thread
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
[backgroundContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
/* Save the background context and handle the save notification */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
//Set a grace period during which background updates can't stack up...
//This number should be more than the longest combo of timeout values in adjustDataInBackground
NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:90];
__lastBackgroundSnapshot = stopDate;
NSLog(#"Stop time = %#", stopDate);
MasterViewController *masterViewContoller = [[MasterViewController alloc] init];
masterViewContoller.managedObjectContext = backgroundContext;
NSLog(#"Successfully fired up masterViewController class");
[masterViewContoller adjustDataInBackground];
NSLog(#"adjustDataInBackground!");
//just in case
[[self locationManager] stopUpdatingLocation];
//save background context
[backgroundContext save:NULL];
NSLog(#"Uploading in background");
//send results to server
postToServer *uploadService = [[postToServer alloc] init];
uploadService.managedObjectContext = backgroundContext;
[uploadService uploadToServer];
//save background context after objects are marked as uploaded
[backgroundContext save:NULL];
//unregister self for notifications
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
In addition, I added the following runloop to my asynchronous URLConnection objects so they stayed alive long enough to finish their business. While it's not the most graceful way of handling it, it works as long as you can handle the failure gracefully if the runloop ends without the server exchange finishing.
A runloop (adjusted for different timeouts depending on the task):
//marks the attempt as beginning
self.doneUpload = [NSNumber numberWithBool:FALSE];
[[uploadAttempt alloc] fireTheUploadMethod];
//if uploading in the background, initiate a runloop to keep this object alive until it times out or finishes
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
//Timeout length to wait in seconds to allow for async background execution
NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:120];
do {
NSLog(#"Waiting for upload to return, time left before timeout: %f", [stopDate timeIntervalSinceNow]);
[[NSRunLoop currentRunLoop] runUntilDate:stopDate];
} while ([stopDate timeIntervalSinceNow] > 0 && self.doneUpload == [NSNumber numberWithBool:FALSE]);
}
Hope this helps anyone who runs into this in the future!

Related

UILocalNotification does not fire after 10 minutes in background

I have a VoIP application. Which is working fine. Call is working in foreground and background.
Following steps are done:
UIBackgroundModes => voip in Info.plist
Configured one of the app’s sockets for VoIP usage.
Before moving to the background, setKeepAliveTimeout:handler: is called
Configured audio session to handle transitions to and from active use.
To ensure a better user experience on iPhone, used the Core Telephony framework to adjust behavior in relation to cell-based phone calls;
To ensure good performance for VoIP app, used the System Configuration framework to detect network changes and allow app to sleep as much as possible.
Now the thing is when application is in background and a call comes then UILocalNotification fires for the following. And user can see a notification with two buttons CANCEL and RECEIVE
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask: bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_main_queue(), ^{
while ([application backgroundTimeRemaining] > 1.0) {
NSString *friend = [self checkForIncomingChat];
if ([friend length]>0) {
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif) {
localNotif.alertBody = [NSString stringWithFormat: NSLocalizedString(#"%#", nil), friend];
localNotif.alertAction = NSLocalizedString(#"Receive", nil);
localNotif.soundName = #"alarmsound.caf";
localNotif.applicationIconBadgeNumber = 1;
[application presentLocalNotificationNow:localNotif];
friend = nil;
}
}
sleep(1);
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}
- (NSString *) checkForIncomingChat {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *incall = [prefs objectForKey:#"incall"];
if ([incall length]>0) {
[prefs setObject:#"" forKey:#"incall"];
return incall;
}
return #"";
};
Now the problem is:
After going to the background by pressing home button application fires UILocalNotification if any call comes within 10 minutes.
After 10 minutes if any call comes it is running in the background. UILocalNotification does not fire, so user can not know anything.
It happens because background task stops after 10 minutes.
How can I manage it or extend background task for long running or restart background task.
More and more answer I have found after searching but nothing works for long running background task.
Please anybody help me. I am trying it since 2 weeks.
It sounds like you have a VoIP socket that you are receiving the call on, so rather than looping and polling for call state, you can just present the local notification at the point that you read the data off the socket.
If the VoIP control socket is TCP, and marked with the appropriate ...NetworkServiceTypeVoIP key, your app will automatically get woken up from suspension for 10 seconds, at which point you can present the local notification.
See Configuring Sockets for VoIP Usage for more information.
Once that is done, all of the code that you shared above can be removed.

iOS HTTP request while in background

It is possible to make HTTP async requests to PHP server while the app being in background?
The app is a location based one, and should gather current location and send the coordinates to server every 5(or other value) minutes. Can I make the http posts to the server even the app is in background? I read lot of thoughts about this, but some of them told that can be done, others that can't be done.
Thanks,
Alex.
It can be done but it is unreliable because you ask the OS for time to send something and it can accept or deny your request. This is what I have (stolen from somewhere on SO):
[...] //we get the new location from CLLocationManager somewhere here
BOOL isInBackground = NO;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
isInBackground = YES;
}
if (isInBackground)
{
[self sendBackgroundLocationToServer:newLocation];
}
- (void) sendBackgroundLocationToServer: (CLLocation *) lc
{
UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:2];
[dictionary setObject:[NSNumber numberWithDouble:lc.coordinate.latitude] forKey:#"floLatitude"];
[dictionary setObject:[NSNumber numberWithDouble:lc.coordinate.longitude] forKey:#"floLongitude"];
// send to server with a synchronous request
// AFTER ALL THE UPDATES, close the task
if (bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}
}
These links will help you out...
iphone - Connecting to server in background

Using multiple beginBackgroundTaskWithExpirationHandler calls

I am trying to follow this previous post here: Best practice to send a lot of data in background on iOS4 device?
And basically, I have a method called getRequest that grabs information from the web server. There are about 50 pieces of data I need from the web server. So at the same time, I have 50 delegate calls to connectionDidFinishLoading. Currently my getRequest looks like:
-(void) getRequestWithURL:(NSString *) requestURL
{
static int getRequest = 0;
NSLog(#"getRequest: %i", getRequest);
getRequest++;
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier taskID;
taskID = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Time remaining: %f", app.backgroundTimeRemaining);
NSLog(#"Background task not completed");
[app endBackgroundTask:taskID];
}];
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:requestURL]];
NSURLConnection *con = [[NSURLConnection alloc] initWithRequest:req delegate:self] ;
[self startRequestWithConnection:con];
[req release];
if (taskID == UIBackgroundTaskInvalid) {
NSLog(#"Failed to create background task identifier");
}
}
Then in my connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// process data from server
// endBackgroundTask:someTaskID???
}
I know you are allowed to have multiple calls of beginBackgroundTaskWithExpirationHandler, but I don't know if what I'm doing in my getRequest method is doing that since I only have one variable __block UIBackgroundTaskIdentifier taskID each time the method is called. And I'm also not sure if I need to call endBackgroundTask in the connectionDidFinishLoading method for each call to getRequest since you are supposed to balance the beginBackgroundTaskWithExpirationHandler with an endBackgroundTask: call. If so, how do I do that since my getRequest doesn't currently have that infrastructure? Do I need 50 ivars in order for the connectionDidFinishLoading method to see the 50 initial calls to getRequest? Thanks.
As you said, you need to balance beginBackgroundTaskWithExpirationHandler call with an endBackgroundTask call.
One solution I have in mind looks like this:
Create a new instance variable
UIBackgroundTaskIdentifier backgroundTaskID;
You are counting the requests anyway so you could also decrement getRequest in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// process data from server
getRequest--;
if (getRequest == 0 && backgroundTaskID != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
}
}
Now the background task gets ended after the last request has been completed. To start only one background task you start it in a method that gets called when the app goes to the background.
You need to listen for the UIApplicationDidEnterBackgroundNotification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
and implement the method
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
if (getRequest > 0) {
backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
}];
}
}
Now you only have one running background task that starts automatically when your app goes to the background and you have running requests that gets ended when all your requests are done.
Another improvement would be to add your network requests to an NSOperationQueue to avoid the manual counting and limit the number of concurrent requests.
The work being done is simple whatever code comes next. The work isn't wrapped up into the Background task. The background task is just an id and a status that tells the iOS framework if you are finished doing your task or not. It's up to

How to get local notification once folder is determined to be not empty?

I would like to know how to get local notifications while my application's NSTimer is firing in the background. My NSTimer checks a particular folder for files every second for 10 minutes in the background. How would I go about receiving a local notification if a file is found?
EDIT : Code :
- (void) createTimeThread: (float) pIntervalTime
{
[NSThread detachNewThreadSelector:#selector(startTimerThread)
toTarget:self withObject:nil];
}
- (void) startTimerThread
{
UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{}];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
myTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(conditionChecking:)
userInfo:nil
repeats:YES];
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[pool release];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}
- (void)conditionChecking:(NSIndexPath *)indexPath
{
NSString *pathForFile = #"/User/Library/Logs/CrashReporter";
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:pathForFile]) { // Directory exists
NSArray *listOfFiles = [fileManager contentsOfDirectoryAtPath:pathForFile error:nil];
if (!listOfFiles || !listOfFiles.count)
{
NSLog(#"No Core Dumps found.....");
}
else
{
NSLog(#"Core Dump(s) found! :%#", listOfFiles);
}
}
}
I believe that you want to notify all other classes that folder is filled with files.
Following steps can do that for you.
write following line in initialization of class where you want to receive notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkFiles:) name:#"FILES_AVAILABLE" object:nil];
Write methods checkFiles with following signature in same class.
-(void)checkFiles:(id)sender
Add following line in timer class when files are available.
[[NSNotificationCenter defaultCenter] postNotificationName:#"FILES_AVAILABLE" object:self];
If this is not helpful then you can use NSUserDefault to store status of application(Files are available or not in you case). OR With if you are interested in design patterns read about Observer Pattern.
In case you want to post notification when your application is in background mode and some process that is still running gets some update then that can be achieved using notification queue. read following link. I am not writing code because code is given in link itself.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Articles/NotificationQueues.html#//apple_ref/doc/uid/20000217-CJBCECJC
Post here if you need more help.

Location Background - Network Connection

I am trying to post data on background and I want to receive response from in regular interval that so that I am trying to use CLLocation Manager . But didUpdateToLocation method calling only once.
Please suggest me !
enter code here
- (void)applicationDidEnterBackgroundUIApplication *)application
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 100 m
[locationManager startUpdatingLocation];
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_ QUEUE_PRIORITY_DEFAULT, 0), ^{
//Do the work associated with the task
// code for Posting Data Here
NSLog(#"backgroundTimeRemaining: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
- (void)locationManagerCLLocationManager *)manager
didUpdateToLocationCLLocation *)newLocation
fromLocationCLLocation *)oldLocation
{
NSLog(#"Location Change");
}
This Method Call Only Once.
Of course, and you should be happy what delegate received message at least once. There is no code which will delay application suspending in your background execution code. Also this kind of code can work about 10 minutes and will be killed by system.
If you need to track user location in background, than maybe you just set this mode for application background operation in Info.plist (UIBackgroundModes and add option location) and your app won't be suspended (only in case of heavy memory usage)