Using multiple beginBackgroundTaskWithExpirationHandler calls - iphone

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

Related

How to increase applicationDidEnterBackground in iPhone?

I call one api in applicationDidEnterBackground, but applicationDidEnterBackground method returns after 5 seconds so how could I increase timer or after api finish then only applicationDidEnterBackground will return all of us suggest use beginBackgroundTaskWithExpirationHandler
But I don't know how to use it can anyone guide me?
Here is my code
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSString *link=[NSString stringWithFormat:#"http://www.askpundit.com/dev/js_chat/getNewRequest.php?updateStatus=%#&clientid=%#",UpdateStatus,[self getSetting:#"Clientid"]];
NSURLRequest *request=[[NSURLRequest alloc]initWithURL:[NSURL URLWithString:link]];
NSURLConnection *connaction=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connaction)
{
responsedata=[NSMutableData data];
NSLog( #"Data Saved");
}
}
Can any one guide me how return applicationDidEnterBackground after my call finish.
As you surmise, beginBackgroundTaskWithExpirationHandler is what you should use. It's very straightforward. This snippet is minimally proofread, and incomplete - but demonstrates the approach.
- (void)applicationDidEnterBackground:(UIApplication *)application {
_completionTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:_completionTask];
_completionTask = UIBackgroundTaskInvalid;
}];
// begin your NSURLConnection, etc.
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if( _completionTask != UIBackgroundTaskInvalid ) {
[application endBackgroundTask:_completionTask];
_completionTask = UIBackgroundTaskInvalid;
}

iOS background Location not sending http request

My app needs to track the users location in the background but it is failing to send a 'get' request. The http request gets sent immediately when the app comes to the foreground. I am using RestKit for all my network requests and I followed this tutorial to setup my background locations service.
In my applicationDidEnterBackground
-(void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgLocationManager = [[CLLocationManager alloc] init];
self.bgLocationManager.delegate = self;
[self.bgLocationManager startMonitoringSignificantLocationChanges];
NSLog(#"Entered Background");
}
and I stopMonitoringSignificantLocationChange in my applicationDidBecomeActive delegate
This is my locationManager delegate where I accept the new updated location and send to my server
-(void) locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(#"I am in the background");
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
// ANY CODE WE PUT HERE IS OUR BACKGROUND TASK
NSString *currentLatitude = [[NSString alloc]
initWithFormat:#"%g",
newLocation.coordinate.latitude];
NSString *currentLongitude = [[NSString alloc]
initWithFormat:#"%g",
newLocation.coordinate.longitude];
NSString *webToken = [[NSUserDefaults standardUserDefaults] stringForKey:#"userWebToken"];
NSLog(#"I am in the bgTask, my lat %#", currentLatitude);
NSDictionary *queryParams;
queryParams = [NSDictionary dictionaryWithObjectsAndKeys:webToken, #"auth_token", currentLongitude, #"lng", currentLatitude, #"lat", nil];
RKRequest* request = [[RKClient sharedClient] post:#"/api/locations/background_update" params:queryParams delegate:self];
//default is RKRequestBackgroundPolicyNone
request.backgroundPolicy = RKRequestBackgroundPolicyContinue;
// AFTER ALL THE UPDATES, close the task
if (bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
The network requests works as planned but it will not get called in the background. Is there any additional steps I need? In my info.plist I have the Required Background modes key and location-services as the value.
EDIT
I also referred to this past SO answer. I ran some tests with putting logs throughout the didUpdateToLocation call and they were all called but the 'get' request was not sent. Instead when I finally launch the app to the foreground it sent all the built of network requests (over 10).
EDIT (2)
I added RKRequestBackgroundPolicyContinue to my request but it did not change my results. (As you can see here in the background upload/download for restkit). I see Restkit initialize the host but fails to send the request until the app becomes active.
ANSWER
RestKit must be doing something that is prohibited in the background. Using an NSURLRequest works perfectly.
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.example.com/api/locations/background_update"]];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:jsonData];
NSHTTPURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
It is fine to use a synchronous request since there is no UI to disrupt with background tasks
Re-creating original suggestion as an answer
Have your try replacing your restKit calls with a stock synchronous NSURLConnection? – dklt Sep 20
I'm using exactly the same code as you and it works for me in RestKit. The only way I could make it work is ny creating a synchronous request (it doesn't make a lot of sense to do it asynchronously in this context anyway!). Please check this code and let us know if it works:
// REMEMBER. We are running in the background if this is being executed.
// We can't assume normal network access.
// bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier
// Note that the expiration handler block simply ends the task. It is important that we always
// end tasks that we have started.
_bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:
^{
[[UIApplication sharedApplication] endBackgroundTask:_bgTask];
}];
// ANY CODE WE PUT HERE IS OUR BACKGROUND TASK
// For example, I can do a series of SYNCHRONOUS network methods (we're in the background, there is
// no UI to block so synchronous is the correct approach here).
NSNumber *latNumber = [NSNumber numberWithDouble:location.coordinate.latitude];
NSNumber *lngNumber = [NSNumber numberWithDouble:location.coordinate.longitude];
NSNumber *accuracyNumber = [NSNumber numberWithDouble:location.horizontalAccuracy];
NSDictionary *params = [NSDictionary dictionaryWithKeysAndObjects:#"lat",latNumber,#"lng",lngNumber,#"accuracy",accuracyNumber, nil];
RKURL *URL = [RKURL URLWithBaseURL:[NSURL URLWithString:SERVER_URL] resourcePath:#"/user/location/update" queryParameters:params];
RKRequest *request = [RKRequest requestWithURL:URL];
request.method = RKRequestMethodGET;
NSLog(#"Sending location to the server");
RKResponse *response = [request sendSynchronously];
if (response.isFailure)
NSLog(#"Unable to send background location, failure: %#", response.failureErrorDescription);
else {
NSError *error = nil;
NSDictionary *parsedBody = [response parsedBody:&error];
if (YES == [[parsedBody objectForKey:#"result"] boolValue]){
NSLog(#"Background location sent to server");
}
else {
//Something went bad
NSLog(#"Failed to send background location");
}
}
// AFTER ALL THE UPDATES, close the task
if (_bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:_bgTask];
_bgTask = UIBackgroundTaskInvalid;
}
I'm almost sure the new thread spawned for your RKClient request is automatically killed after invoking it.
When you're application is running in the background you can finish a HTTP request you started before you entered the background but you cannot initiate a new request. You can only initiate certain network operations while in the background (voip, newsstand).

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

Background task block function not finishing

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!

NSOperation for drawing UI while parsing data?

Hope you guys can help me :)
In the main thread, I create a NSOperation and add it to a queue.
What that operation do is connect to a data server with NSURLConnection, save the receivedData and parse it.
Operation.m
- (void)start
{
NSLog(#"opeartion for <%#> started.", [cmd description]);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"multipart/form-data; boundary=%#", m_BOUNDARY] forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:_postData];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (_connection == nil)
[self finish];
}
Then in this NSURL delegate method I parse the data I've just received from server.
Operation.m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self parseItems];
}
In the data, I can found items like, for instance, screenItem, CellItem, TextItem that I send to the main thread for drawing them while arriving. (I create a UITableView if an itemTable arrives, or I create a UIWebView if an itemWeb arrives)
Using this for sending item to main thread:
Operation.m
- (void) parseItems
{
while ([_data length] > 0)
{
NSInteger type = [self _readByte];
switch (type)
{
case SCREEN:
{
[self _send: [self _readScreen]];
break;
}
case CELL:
{
[self _send: [self _readCell]];
break;
}
// ... A lot of different items
}
}
}
- (void)_send:(CItem*)_item
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"newItem" object:_item];
}
Then in notification receiver:
AppDelegate.m
- (void) _newItemArrived:(NSNotification *) notification
{
[self performSelectorOnMainThread:#selector(processItem:) withObject:[notification object] waitUntilDone:NO];
}
My problem is that the UI is not painted until NSOperation finish. I thought that NSOpertion, being a different thread, would not block the main thread, but believe that is what is happening.
Some tips for this issue?
Thanks a lot for reading!
Are you using NSOperationQueue?
Check out this answer to the question NSOperation blocks UI painting? for a simple example of how to update the UI with a notification from an NSOperation running asynchronously on another thread.
UPDATE
NSURLConnection supports asynchronous connections by itself with a delegate. You should use this. If you have specific issue(s), you should describe those.
Check out the ASIHTTPRequest library.
If you really want to use this approach, you could trying running NSURLConnection synchronously (using the class method sendSynchronousRequest:returningResponse:error:). Your app would remain responsive since the connection is on a background thread. However, you would not be able to update anything until all the data is received.
So I know this is a pretty old question but I ran into the same issue and after hours of going through documentation and blogs I found a great solution in this post from Wim Haanstra http://www.depl0y.com/?p=345
Putting your NSOperation in an infinite loop until you get data back should do the trick!