I'm starting a background task in -applicationDidEnterBackground that uploads data to my server, if the user has changed settings relevant to the push notifications. When the user changes a setting I set a static BOOL to YES and only send the changes when the app enters the background. I pass the a block ending the task to the method so when reaching connectionDidFinishLoading it calls it and ends the task.
It works most the times on the simulator, but doesn't work on the actual device.
Relevant code:
self.bgTask = [application beginBackgroundTaskWithExpirationHandler:^
{
[application endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
[PushInfo checkDirty:^{
NSLog(#"push info sent");
[application endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
});
...
// in PushInfo.m :
typedef void (^VoidBlock)();
static BOOL dirty;
+ (void) checkDirty:(VoidBlock)endBlock
{
if(dirty)
{
PushInfo *pi = [[PushInfo alloc] init];
NSLog(#"sending pushinfo"); // This code is always reached
[pi setEndBlock:endBlock];
[pi updatePushInfo];
}
else
endBlock();
}
- (void) updatePushInfo
{
...
// Create a NSURLConnection to send the data
...
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
...
NSLog(#"sent push info");
dirty = NO;
if(endBlock)
{
endBlock();
}
}
Am I missing anything ?
EDIT : even when it does send the information to the server on the simulator, the static variable is still YES for some reason...
Try moving your code to:
-(void) applicationWillResignActive:(UIApplication *)application
I believe having it in applicationDidEnterBackground is too late.
Checked the docs, and you are right, it should be fine in applicationDidEnterBackground.
Another suggestion, try moving beginBackgroundTaskWithExpirationHandler inside the dispatch_async block. It may have to be started in the same thread but didn't see this explicitly stated in the documentation.
ended using +sendSynchronousRequest:returningResponse:error:
Related
I need to call a web service in every minute and parse the data when app is in background state.
Since the APP uses location service I have enabled background mode for update Location.
I tried calling location update by using a timer background task, but it not working.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:60
target:self.locationManager
selector:#selector(startUpdatingLocation)
userInfo:nil
repeats:YES];
}
Is there any way to implement this with less battery consumption.
I referred this link
I'm not getting which solution is better here.
AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : NSObject {
// Instance member of our background task process
UIBackgroundTaskIdentifier bgTask;
}
#end
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
// bgTask is instance variable
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
if ([application backgroundTimeRemaining] > 1.0) {
// Start background service synchronously
[[BackgroundCleanupService getInstance] run];
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}
There are couple key lines in the above implementation:
The first is the line bgTask = [application beginBackgroundTaskWithExpirationHandler..., which requests additional time to run clean up tasks in the background.
The second is the final code block of the delegate method beginning with dispatch_async. It's basically checking whether there's time left to run an operation via the call [application backgroundTimeRemaining]. In this example, I'm looking to run the background service once but alternatively, you can use a loop checking on the backgroundTimeRemaining on each iteration.
The line [[BackgroundCleanupService getInstance] run] will be a call to our singleton service class, which we'll build right now.
With the app delegate ready to trigger our background task, we now need a service class that will communicate with the web server. In the following example, I'm going to a post a fictitious session key and parse a JSON encoded response. Also, I'm using two helpful libraries to make the request and deserialize the returned JSON, specifically JSONKit and ASIHttpRequest.
BackgroundCleanupService.h
#import <Foundation/Foundation.h>
#interface BackgroundCleanupService : NSObject
+ (BackgroundCleanupService *)getInstance;
- (void)run;
#end
BackgroundCleanupService.m
#import "BackgroundCleanupService.h"
#import "JSONKit.h"
#import "ASIHTTPRequest.h"
#implementation BackgroundCleanupService
/*
* The singleton instance. To get an instance, use
* the getInstance function.
*/
static BackgroundCleanupService *instance = NULL;
/**
* Singleton instance.
*/
+(BackgroundCleanupService *)getInstance {
#synchronized(self) {
if (instance == NULL) {
instance = [[self alloc] init];
}
}
return instance;
}
- (void)run {
NSURL* URL = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.example.com/user/%#/endsession", #"SESSIONKEY"]];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:URL];
[request setTimeOutSeconds:20]; // 20 second timeout
// Handle request response
[request setCompletionBlock:^{
NSDictionary *responseDictionary = [[request responseData] objectFromJSONData];
// Assume service succeeded if JSON key "success" returned
if([responseDictionary objectForKey:#"success"]) {
NSLog(#"Session ended");
}
else {
NSLog(#"Error ending session");
}
}];
// Handle request failure
[request setFailedBlock:^{
NSError *error = [request error];
NSLog(#"Service error: %#", error.localizedDescription);
}];
// Start the request synchronously since the background service
// is already running on a background thread
[request startSynchronous];
}
#end
may be helped
I am using AFNetwork (its base on NAFDownloadRequestOperation) and my task in downloading multiple zip files one by one from amazon bucket.
When app is in foreground every thing is working very well, but when app goes in background mode that time downloading is running for some time only and it will automatically stop. I read some blog about it in that I get that following method called before downloading will stop.
[self.operationUpdate setShouldExecuteAsBackgroundTaskWithExpirationHandler:^{
NSLog(#"downloading will stop");
}];
Problem in background mode downloading is automatically stop
What I want: If downloading is stop in background and when app again comes to foreground I need to resume downloading from that point.
I also use following code in AppDelegate but I am not understand how to resume previous downloading.
__block UIBackgroundTaskIdentifier backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^(void) {
[application endBackgroundTask:backgroundTaskIdentifier];
NSLog(#"applicationWillResignActive");
[__SERVER_INSTANCE cancellAllDownloading];
// [[YourRestClient sharedClient] cancelAllHTTPOperations];
}];
If any one have any solution please let me know, Thanks in advance.
You should use AFDownloadRequestOperation
Your request will look like
AFDownloadRequestOperation *operation = [[AFDownloadRequestOperation alloc] initWithRequest:request targetPath:path shouldResume:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operations addObject:operation];
After you restart your app, and generate the request having a same url, it will resume downloading. "shouldResume:YES" works.
So, on your background task you can recreate request to finish download
__block UIBackgroundTaskIdentifier backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^(void) {
[application endBackgroundTask:backgroundTaskIdentifier];
NSLog(#"applicationWillResignActive");
[__SERVER_INSTANCE cancellAllDownloading];
// recreate here your request to finish fownload,
//or recreate in when app will enter foreground
}];
Hope this helps
The background downloading stops because after sometime because you might not have enable Background Fetch in Capabilities in xcode.
Please see attached screenshot
And Appdelegate You have write this code,
-(BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return true;
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
completionHandler(UIBackgroundFetchResultNewData);
}
OR
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
NSTimeInterval ti = [[UIApplication sharedApplication]backgroundTimeRemaining];
NSLog(#"backgroundTimeRemaining: %f", ti);
// just for debug
}];
}
Hope this will help!
I have a app that fetch some content from server via REST api after every 5 mins in background using the UIBackgroundTaskIdentifier. My problem is that this works fine for 1,2 hours and then after it is expired it never re starts the background task. The code I am using is given below,
In AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIApplication* app = [UIApplication sharedApplication];
self.expirationHandler = ^{
[app endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
// self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
NSLog(#"Expired");
self.jobExpired = YES;
while(self.jobExpired) {
// spin while we wait for the task to actually end.
[NSThread sleepForTimeInterval:1];
}
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Restart the background task so we can run forever.
[self startBackgroundTask];
};
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
[self monitorBatteryStateInBackground];
}
- (void)monitorBatteryStateInBackground
{
NSLog(#"Monitoring update");
self.background = YES;
[self startBackgroundTask];
}
- (void)startBackgroundTask
{
NSLog(#"Restarting task");
// Start the long-running task.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// When the job expires it still keeps running since we never exited it. Thus have the expiration handler
// set a flag that the job expired and use that to exit the while loop and end the task.
while(self.background && !self.jobExpired)
{
[self uploadPhotostoServer];
[NSThread sleepForTimeInterval:240.0];
}
self.jobExpired = NO;
});
}
In expired section it do come but never calls the method [self startBackgroundTask]
Any help will be much appreciated.
My Apple Watch app sends a message to the companion iPhone app. In the main app's handleWatchKitExtensionRequest, I send a request to the server:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
if ([[userInfo objectForKey:#"request"] isEqualToString:#"getPendingChallenge"]) {
[MyClient getPendingNotifications:someId withDomain:host withSuccessBlock:^(id responseObject) {
// process responseObject
...
reply(response);
return;
} withFailureBlock:^(NSError *error, NSString *responseString) {
// error handling
return;
}];
}
}
getPendingNotifications above is just a regular network GET request using AFNetworking.
It all works well when the app is active. Because this network request is used to populate the UI on my Apple Watch, I do not wish the main app to be active. However, when the main app on iPhone is in background, I can see the network request being sent out, but the withSuccessBlock or withFailureBlock callback blocks in the above code never gets triggered.
Can the phone app receive network request responses in background mode? If so, what am I doing wrong?
I have found a solution online that works for me, a post (http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply) by Brian Gilham.
And here's the code that works for me.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// There is a chance that the iOS app gets killed if it's in the background
// before it has a chance to reply to Apple Watch.
// The solution is to have the app respond to the request asap, then complete other tasks.
// The following code begins – and ends, after two seconds – an empty background task right at the beginning of this delegate method
// Then we kick off a background task for the real work
// For more details see http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
if ([[userInfo objectForKey:#"request"] isEqualToString:#"getPendingChallenge"]) {
[self handleWatchKitGetPendingChallengeRequest:reply];
}
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
- (void)handleWatchKitGetPendingChallengeRequest:(void (^)(NSDictionary *))reply {
...
[MyClient getPendingNotifications:someId withDomain:host withSuccessBlock:^(id responseObject) {
// process responseObject
reply(response);
return;
} withFailureBlock:^(NSError *error, NSString *responseString) {
// error handling
reply(nil);
return;
}];
}
Try to send the request as a synchronous request.
I guess that your request is asynchronous request (as it should be in regular cases). The problem that in background mode, the device will lunch your app in background thread, and you created a new thread for the request.
I would like to save data when app goes in background. I am doing cancelling NSOperation and saving data in applicationDidEnterBackground. But it does not complete execution.
How can I complete this before my app goes in background?
Code :
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//FUNCTION_START
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[self performSelectorOnMainThread:#selector(dispatchStateNotification:)
withObject:[NSNumber numberWithInt:666]
waitUntilDone:YES ];
// Write to core data
GWSCoreDataController *dataController = [GWSCoreDataController sharedManager];
NSError *error;
if (![dataController.managedObjectContext save:&error]) {
NSLog(#"Error while saving data to Core Data: %#", [error localizedDescription]);
}
// FUNCTION_END
}
-(void)dispatchStateNotification:(NSNumber *)value {
[[NSNotificationCenter defaultCenter] postNotificationName:APPLICATION_ENTERED_BACKGROUND_NOTIFICATION object:value];
}
You can start a background task, and do your cleanup stuff
- (void)applicationDidEnterBackground
{
UIBackgroundTaskIdentifier bgTaskId =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTaskId];
}];
// Start cleanup
.......
[[UIApplication sharedApplication] endBackgroundTask: bgTaskId];
}
I had the same issue and had to put the save call in applicationWillResignActive instead - didEnterBackground just doesn't seem to have the complete CoreData to save with...
I have solved this problem for my requirement like below. Added one flag and run while loop till this flag will not become false. As soon as my task will get complete or app comes in foreground I have marked this flag as false.
// start the task asynchronously which is written into the block on new thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//this loop runs continuously while flag is YES.
while(appDidEnterBackground){
sleep(1);
}//end of while
//ends the background task.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});//end of dispatch queue