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
Related
I have some NSOperations that are started regularly in my application. They should complete even when the the application is put to background. For this, I'm using the beginBackgroundTaskWithExpirationHandler method.
Am I supposed to use the beginBackgroundTaskWithExpirationHandler/ endBackgroundTask: every time I start my task even if the app is not going to background? Or am I supposed the call the begin/end methods only when I detected a UIApplicationDidEnterBackgroundNotification?
Option 1: Use background task every time
/**
* This method is called regularly from a NSTimer
*/
- (void)processData
{
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}];
NSOperation *operation = ...
[self.queue addOperation:operation];
operation.completionBlock = ^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
};
}
Option 2: Use background task only when the application is about to go to background
/**
* This method is called regularly from a NSTimer
*/
- (void)processData
{
NSOperation *operation = ...
[self.queue addOperation:operation];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"EnterBackgroundFlushTask" expirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}];
// wait for all operations to complete and then
// let UIApplication know that we are done
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
}
Answering my own question. From the Apple Docs:
You do not need to wait until your app moves to the background to
designate background tasks. A more useful design is to call the
beginBackgroundTaskWithName:expirationHandler: or
beginBackgroundTaskWithExpirationHandler: method before starting a
task and call the endBackgroundTask: method as soon as you finish. You
can even follow this pattern while your app is executing in the
foreground.
Other Apple API reference:
You should call this method at times where leaving a task unfinished might be detrimental to your app’s user experience.
You can call this method at any point in your app’s execution.
Option2 is correct option.Here is code from Apple document for your reference.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// 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.
[self processData];
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Apple developer Guide
I have added data fetching operations to NSOperationQueue .During the process, I just put the device to sleep mode and the process gets stopped. I surfed stack overflow an get some basic ideas.I need help to continue my fetching process without any interruption when device moves to sleep mode.Help appreciated !!
NSManagedObjectContext *managedObjectContext=((AppDelegate *)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSOperationQueue *downLoadQueue=((AppDelegate *)[[UIApplication sharedApplication] delegate]).downloadqueue;
//Fetch operation
if([fetchqueue count]>0)
{
Queue *queue=[fetchqueue objectAtIndex:0];
queue.status=#"INP";
[managedObjectContext performBlockAndWait:^{
NSError * error = nil;
if (![managedObjectContext save:&error]){
NSLog(#"Unresolved error while loading3");
}
}];
DownloadOperation *downloadOp=[[DownloadOperation alloc]init];
downloadOp.queue=queue;
[downLoadQueue addOperation:downloadOp];
}
}
The downLoadQueue started its execution. It is fetching data from server meanwhile the device goes to sleep and the execution stops. I don't know how to continue in this block applicationDidEnterBackground. How can I get my lengthy downloading process during sleep mode?
Also tried this one..But the operation was not resumed.
//code follows
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSOperationQueue *downLoadQueue;
downLoadQueue=((AppDelegate *)[[UIApplication sharedApplication] delegate]).downloadqueue;
[downLoadQueue waitUntilAllOperationsAreFinished];
});
}
Please suggest any ideas.
I think the only way to do this is background task. Take a look at apple docs about this. There are also a lot of answers on stackowerflow how to implemet such feature.
I think in your case you can try next code to execute NSOperations a in backgraund task:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// 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.
//Start your NSOperationQueue if it's not executing
//Lock current thread while operations are executing
[queue waitUntilAllOperationsAreFinished];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
I have been using https://github.com/Jawbone/UPPlatform_iOS_SDK for a while now
and getting the user's daily steps.
I wanted to pull data from the Jawbone API when the app is in background state.
I have followed this tutorial : http://www.devfright.com/ios-7-background-app-refresh-tutorial/
To call this method :
[UPMoveAPI getMovesWithLimit:10U completion:^(NSArray *moves, UPURLResponse *response, NSError *error) {
NSLog(#"This is not getting executed in background");
}];
The jawbone session is successfully verified and seems that my session is active.
But I get no response and the above NSLog doesn't get executed in background.
Please I have tried contacting Jawbone support, seems that they are not replying back.
Anyone experienced the same, please help.
Try below code options:
Option-1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[UPMoveAPI getMovesWithLimit:10U completion:^(NSArray *moves, UPURLResponse *response, NSError *error) {
NSLog(#"This is not getting executed in background");
}];
}
Option-2
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// 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.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
Also note that you require to add UIBackgroundModes in info.plist
Hope this helps.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[_dataStore saveChanges];
[_sync syncWithServerWithDate:[NSDate dateWithTimeIntervalSince1970:timestamp]];
}
-(void)syncWithServerWithDate:(NSDate *)date
{
void(^postCompletionBlock)(FTjsonEvents *obj, NSError *error) = ^(FTjsonEvents *serverEvents, NSError *error) {
...
NSLog(#"Post Completion block finished!");
};
void(^completionBlock)(FTjsonEvents *obj, NSError *error) = ^(FTjsonEvents *serverEvents, NSError *error) {
....
NSLog(#"Fetch finished!");
[self postRecordsSinceLastServerSyncTimestamp:[date timeIntervalSince1970] WithCompletion:postCompletionBlock];
};
NSLog(#"Syncing data...");
[self fetchRecordsByDate:date WithCompletion:completionBlock];
}
I would like to sync with the server to fetch and post the latest data to.
Since this is happening via async completion blocks, it seems that my classes get garbage collected once I press home button. The sync never reaches the server.
However the local coredata is easily saved when doing this: [_dataStore saveChanges];
Is there a way to keep the async sync alive in the background until its completed?
Your implementation of applicationDidEnterBackground: has approximately five seconds to perform any tasks and return. If you need additional time to perform any final tasks, you can request additional execution time from the system by calling beginBackgroundTaskWithExpirationHandler:. In practice, you should return from applicationDidEnterBackground: as quickly as possible. If the method does not return before time runs out your app is terminated and purged from memory.
You should perform any tasks relating to adjusting your user interface before this method exits but other tasks (such as saving state) should be moved to a concurrent dispatch queue or secondary thread as needed. Because it's likely any background tasks you start in applicationDidEnterBackground: will not run until after that method exits, you should request additional background execution time before starting those tasks. In other words, first call beginBackgroundTaskWithExpirationHandler: and then run the task on a dispatch queue or secondary thread.
Here's an example implementation:
#interface XXAppDelegate (BackgroundStuff)
#property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTask;
#end
#implementation
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Request additional background time.
self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:self.backgroundTask];
}];
// Start background task.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Background code goes here
//Cleanup background task id
[application endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
});
}
#end
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: