In my application, the user needs to save some information to my database.
[myUser saveInBackground:^(BOOL success){
if(success){
// Do smthing
}
else{
// Alert the user
}
}];
For now it's working fine and when something wrong happenned during the save I'm able to alert the user only if the application is still open and active.
Let's say that the request to my database lasts about 10 sec (bad network for instance) and the user decides to leave the app, my completion block doesn't get fired.
How can I alert the user that the save failed when he has already left the app ?
Thanks
Take a look at Background Modes. This example is taken from the mentioned documentation:
- (void)yourSavingMethod {
UIApplication *application = [UIApplication sharedApplication];
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Unfortunately, timeout..
// Clean up any unfinished task.
// 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), ^{
// Save your data.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
You can call -beginBackgroundTaskWithExpirationHandler: when you really need it. But don't forget to end a task. Explore documentation for more details.
Related
The objective is to load ~20k entities on application load (in background thread). Then after load I want to show those in a UITableView. To this purpose with such big amount of objects I shall use fetchedResultController with batching set to like 100?
This is the way I'm trying to achieve it:
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for(int i = 0; i <20000; i++){
Object *object = [Object MR_createInContext:localContext];
object.number = i;
}
} completion:^(BOOL success, NSError *error) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
[self finishedSaving];
}];
Then in finishedSaving
- (void)finishedSaving
{
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
self.fetchResultController = [Object MR_fetchAllGroupedBy:nil withPredicate:nil sortedBy:#"name" ascending:YES inContext:context];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
}
}
I'm having UI freeze on that method. Any ideas to improve or fix?
By default Magical Record has a batch size of 20 (see kMagicalRecordDefaultBatchSize within the source code).
So, with the following size, even if there a lot of objects to grab, the UI should not freeze. NSFetchedResultsController does the right job using a lazing loading mechanism for you. It grabs the first 20. If you scroll, it grabs other 20, and so on.
My guess (I don't know what Magical Record does) is that your saving code (the first snippet) is not running in a background thread or it is not running correctly. Could you provide the entire source code for it?
About the first snippet, do you need to run your task in the background when the app has enter the foreground? Just to be sure, using begin... and end... it does not mean to run a task in the background but just tells the system that you're in the middle of doing something (important for you) that would like to complete in the background if that's is ok.
Update 1
I investigated a bit on MR source code and here the results.
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// This block runs in background thread
} completion:^(BOOL success, NSError *error) {
// This block runs in main thread
}];
So, does your Object only contain a number attribute?
My app needs to do some internet related operation then pop that view when I send the application to background. And then the root view controller fetches some data and updates the collection view. Do you have any idea how can I solve following issues related to above operations(by the way, I use local notifications to start the process):
1) UI related operations(popping current view controller) seems to fail in the background.
2) When I pop my view to root view controller, Root view has some nsurlconnection which sends data to its delegate. Since long running tasks run in global queue, nsurlconnection seems to fail sending any information to its delegate.
I use the following code for this process:
UIBackgroundTaskIdentifier __block bgTask;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performOperation];
NSLog(#"Operation finished");
});
As matt pointed out, you can't do UI operations like popping the view controller when the app is in the background. When the app comes back to the foreground (e.g. the user taps on the icon again), the pop may take place then (if the app wasn't completely terminated in the interim).
I assume matt's observation answered your second question. If it didn't, please clarify what you mean. But this use of UIBackgroundTaskIdentifier doesn't care whether you used global queues or custom queues or whatever. The only restriction is that some UI operations will not take place, so anything contingent upon, for example, viewDidAppear, won't take place.
As an aside, I wanted to point out that you really want to call endBackgroundTask when the code that is doing the task completes, which your code sample does not appear to do. See the Executing a Finite-Length Task in the Background of the App States and Multitasking chapter of the iOS App Programming Guide.
Thus, you might do something like:
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Operation did not finish!!!");
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performOperation];
NSLog(#"Operation finished");
if (bgTask != UIBackgroundTaskInvalid) {
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
I have a decryption and encryption method in my app. When the app enters the background, a file is encrypted. If the app entered the foreground, the file will be decrypted. Thats the main story. Both parts take some time and a lot of memory. In the background, Im doing something like that.
encryptionTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// Wwe took too long. Stop task.
}];
In the applicationWillEnterForeground, Im doing the following:
if (encryptionTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:encryptionTaskId];
}
I think that works well. But my problem is when someone clicks the home button during the applicationWillEnterForeground is decrypting my file. Again the applicationDidEnterBackground is starting, but my decrypting is not finished. What is the best way to handle that. Is it possible to also wait until the foreground finished?
Are you able to detect if the file is currently being accessed or being encrypted / decrypted? If so, create a timer to do the encryption / decryption again in a few seconds after the previous method has stopped.
UPDATE:
Please see http://developer.apple.com/library/ios/DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW28 you should be able to use beginBackgroundTaskWithExpirationHandler:. Which will buy you extra time to finish processing before your app moves to the background.
UPDATE 2:
Make sure encryptionTaskId is an instance variable
- (void)applicationDidEnterBackground:(UIApplication *)application
{
encryptionTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
encryptionTaskId = UIBackgroundTaskInvalid;
}];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
encryptionTaskId = UIBackgroundTaskInvalid;
}
Then in applicationDidEnterForeground you will want to check if encryptionTaskId is still working and if so create a timer to try again shortly to decode it.
In my app I am syncing my data with the server when a user closed the app (i.e. it enters the background). This process takes about a minute and involves various calls to the server to upload data, so I am choosing to perform it as a background thread.
- (void)applicationDidEnterBackground:(UIApplication *)application {
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[asynchronousAPIController processQueueOrWaitWithIsBackgroundSync:true];
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
This works fine, unless a user re-enters the foreground during this sync (i.e. during that first minute). What I want is for the background sync to terminate completed - stop syncing even if it's midway through it's sync. When the app next goes into the background, it can restart the whole process from scratch, no problem.
I've tried using this:
- (void)applicationWillEnterForeground:(UIApplication *)application {
if (bgTask != UIBackgroundTaskInvalid) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
But it doesn't stop the syncing - that just keeps going. Almost as if once it's started it just keeps going.
I'm suspicious that it might be because the object controlling the sync is a Singleton - i.e. instead of being a class object which could just be destroyed, it's a singleton and so might be staying alive for that reason. Could that make a difference?
Or am I just doing something wrong?
Any advice, much appreciated.
You will need to do the background work in a NSOperation. I would create a NSBlockOperation and add it to a NSOperationQueue. That way a background thread is spawned automatically. For complex operations create a NSOperation subclass.
Inside the NSOperation code check if the operation is cancelled regularly (for example inside a for loop) and exit the the code block if yes (in the for loop: if ([operation isCanceled]) break;).
In applicationWillEnterForeground just cancel the operation.
I'm trying to implement a passcode lock feature in my app that lets the user choose how much time must go by before the passcode is required for reentry (similar to the passcode functionality of the OS). So for example the user may be able to select that they want the passcode to be required 5, 10, 20 minutes after exiting the app into the background.
I've tried to deal with presenting a passcode view in different ways, but it is often difficult to figure out the best way to present it, and so I had the idea that perhaps it is best to terminate the app after the time is up, and therefore I would only have to present the passcode screen when the app is launched.
Is this possible to do? I had two thoughts about ways to approach this.
1) Have an NSTimer within the app delegate, start it when the app goes into the background, and then when/if the timer reaches the set number of minutes, then terminate the app? I could see a number of things going wrong with this, for example if the OS terminated the app to free up memory sooner than the timer finished. Although that wouldn't be a huge issue.
2) Set an instance of NSDate when the app goes into the background. Then when the app is being launched, see if this date is more than x minutes ago, and present the passcode entry screen depending on that.
I feel like both of these are a little off. I'm inexperienced with Timers, RunLoops, etc, so any advice is appreciated.
Option 2 seems to be a good solution that we have used with success.
Option 2. Use the ApplicationDelegate Lifecycle methods to drive it.
application:didFinishLaunchingWithOptions:
applicationDidBecomeActive:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationWillTerminate:
applicationDidFinishLaunching:
In the applicationWillResignActive method persist the current timestamp to your UserDefaults, and in the applicationWillEnterForeground check this against the current time and if the passcode interval has passed, present your passcode.
(probably best to clear the timestamp when you are active to minimise the chance of false triggering on receiving calls and SMS etc)
Depending on sensitivity you may want to prepare your views before entering foreground to obscure sensitive data, so they do not return in the unlocked state.
you can follow both for better result. for example use option 2 when app active from didFinishLaunchingWithOptions: and option 1 when application enable from
- (void)applicationDidBecomeActive:(UIApplication *)application or - (void)applicationWillEnterForeground:(UIApplication *)application
option 1-Easiest way is to schedule a NSTimer on the background run-loop. I suggest that the following code is implemented on your application delegate, and that you call setupTimer from applicationWillResignActive:.
- (void)applicationWillResignActive:(UIApplication *)application
{
[self performSelectorInBackground:#selector(setupTimerThread) withObject:nil];
}
-(void)setupTimerThread;
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSTimer* timer = [NSTimer timerWithTimeInterval:10 * 60 target:self selector:#selector(triggerTimer:) userInfo:nil repeats:YES];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forModes:NSRunLoopCommonModes];
[runLoop run];
[pool release];
}
-(void)triggerTimer:(NSTimer*)timer;
{
// Do your stuff
}
in appDelegate .h
UIBackgroundTaskIdentifier bgTask;
in appDelegate .m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication* app = [UIApplication sharedApplication];
// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[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.
// Synchronize the cleanup call on the main thread in case
// the expiration handler is fired at the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
NSLog(#"app entering background");
/*
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.
*/
}
OR you could run the NSTimer on a background thread by with something like this (I am intentionally leaking the thread object):
-(void)startTimerThread;
{
NSThread* thread = [[NSThread alloc] initWithTarget:self selector:#selector(setupTimerThread) withObject:nil];
[thread start];
}
try with this above code. we use both options its works fine for us. good luck