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;
}
});
Related
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.
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?
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 add support for completing a task after exiting the foreground for my iOS App. All the tutorials I saw on the internet point me to writing something like this in the Application Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application {
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler: ^ {
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//DO STUFF IN BACKGROUND HERE
MyTask *task = [[MyTask alloc] initWithURL:#"http://google.com"];
[task setDelegate:self];
[task start];
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
}
}
The task is able to start, but after it starts, the next line gets processed and my app is killed. I am able to tell when MyTask stops due to a delegate call, but how do I change my program so that the background task gets set to invalid after the delegate gets called. Would I move
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
to the delegate function, or do I need to do something else.
Thanks in advance for your help,
Guvvy
You're already in a background thread per your call to dispatch_async. That method will return immediately, but your block will continue to run.
Your problem seems to be that your start method runs another asynchronous process, so that method returns immediately, and your background task is killed before [task start] is able to do anything meaningful.
If you go the route of killing background_task in your delegate, you'll also have to create an ivar to store background_task so your delegate methods can get to it. That seems a little heavy handed.
Since you're already in a background thread, my suggestion would be for you to refactor [task start] so it's synchronous. That way, all the meaningful work will be performed before the next line is processed and background_task is killed. This seems cleaner, and everything for your background task is nicely wrapped in the block sent to dispatch_async.
You can completely remove following code:
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
When your task is expired, iOS will call your handlerExpiration method to kill it.
For more information, please visit Background for iOS
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