Can we call the method after the application has been minimized? - iphone

iOS
Can we call the method after the application has been minimized?
For example, 5 seconds after was called applicationDidEnterBackground:.
I use this code, but test method don't call
- (void)test
{
printf("Test called!");
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self performSelector:#selector(test) withObject:nil afterDelay:5.0];
}

You can use the background task APIs to call a method after you've been backgrounded (as long as your task doesn't take too long - usually ~10 mins is the max allowed time).
iOS doesn't let timers fire when the app is backgrounded, so I've found that dispatching a background thread before the app is backgrounded, then putting that thread to sleep, has the same effect as a timer.
Put the following code in your app delegate's - (void)applicationWillResignActive:(UIApplication *)application method:
// Dispatch to a background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Tell the system that you want to start a background task
UIBackgroundTaskIdentifier taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// Cleanup before system kills the app
}];
// Sleep the block for 5 seconds
[NSThread sleepForTimeInterval:5.0];
// Call the method if the app is backgrounded (and not just inactive)
if (application.applicationState == UIApplicationStateBackground)
[self performSelector:#selector(test)]; // Or, you could just call [self test]; here
// Tell the system that the task has ended.
if (taskID != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskID];
}
});

Related

performSelectorInBackground not working when App is minimized

I'm trying to keep the App running on task in background even if the User minimized and started working in other stuff on the ios device, but my method is hit only when the App is active on the screen. Am I missing something obvious here?
-(void) viewDidAppear:(BOOL)animated
{
[self performSelectorInBackground:#selector(RunMethodEvenWhenMinimized) withObject:nil];
}
-(void)RunMethodEvenWhenMinimized
{
while(YES)
{
//My Code
sleep(10);
}
}
The problem is that you seem to be confusing background processing, and NSObject's background thread helper function. You aren't actually specifying that your method should run in the background, rather, that it will run on a thread that isn't the main thread. It's recommended that you use -[UIApplication beginBackgroundTaskWithExpirationHandler:]; to notify the system that your application needs to exceed it's allotted time in the foreground for method execution. Your code will end up looking something like this when all is said and done:
-(void) viewDidAppear:(BOOL)animated
{
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
[self RunMethodEvenWhenMinimized];
[app endBackgroundTask:bgTask];
}
-(void)RunMethodEvenWhenMinimized
{
while(YES)
{
//My Code
sleep(10);
}
}

UI-updates with Background tasks on iOS

I have a synchronisation process which sometimes (even rarely) takes 20 seconds. I used to call the selector in the background. There is a progress indicator which stops animating on a notification that the sync is finished, and with the code below it changes straight away:
[self performSelectorInBackground:#selector(updateFunction) withObject:nil];
But in order to allow the progress to continue I decided to use the background task:
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance
__block UIBackgroundTaskIdentifier background_task; //Create a task object
background_task = [application beginBackgroundTaskWithExpirationHandler: ^ {
[application endBackgroundTask: background_task]; //Tell the system that we are done with the tasks
background_task = UIBackgroundTaskInvalid; //Set the task to be invalid
//System will be shutting down the app at any point in time now
}];
//Background tasks require you to use asyncrous tasks
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Perform your tasks that your application requires
NSLog(#"\n\nRunning in the background!\n\n");
[self updateFunction];
[application endBackgroundTask: background_task]; //End the task so the system knows that you are done with what you need to perform
background_task = UIBackgroundTaskInvalid; //Invalidate the background_task
NSLog(#"Finished in the background!");
});
But now, when the task is finished the notifications is sent (and executed) but the cell in the table doesn't update until (sometimes) a significant amount of time. So it seems to me that the 'needs display' is not triggered when coming from a background task or so, and is only updated at a regular refresh interval or so.
Is there a way to have the table cell update using background tasks, or am I missing something?
Edit:
The notification listener is:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshBttn)
name:#"finishedUpdate"
object:nil];
The code is:
- (void) refreshBttn {
NSLog(#"Refresh Buttun action");
iDomsAppDelegate *delegate = (iDomsAppDelegate *)[[UIApplication sharedApplication] delegate];
if([delegate updating]){
[_updateBttn setHidden:TRUE];
[_activityIndicator startAnimating];
} else {
[_updateBttn setHidden:FALSE];
[_activityIndicator stopAnimating];
}
[self setNeedsDisplay];
[self setNeedsLayout];
}
In order for the UI to update you have to call the function on the main thread from your background thread like so:
[self performSelectorOnMainThread:#selector(updateFunction) withObject:nil waitUntilDone:NO];
If you need to pass any variables you can go ahead and add those in the -withObject
You can ensure that a notification is received from any queue you want with...
[[NSNotificationCenter defaultCenter] addObserverForName:SomeNotification
object:sender
queue:operationQueue
usingBlock:^(NSNotification *){
// Your notification will be processed in this block, and it will be
// received on the notification queue you specify, regardless of what
// thread the sender was running when the notification was posted.
}];
If you want to make sure it is received on the main thread, you can use
[NSOperationQueue mainQueue]
You need to split up your logic between background and foreground threads. The notification you're sending should be sent from the main thread, so that your UI objects listening to it can update themselves safely. So, break up updateFunction into two pieces. Perhaps something like this:
NSLog(#"\n\nRunning in the background!\n\n");
[self updateFunctionBackground];
dispatch_async(dispatch_get_main_queue(), ^{
// send your notification here instead of in updateFunction
[[NSNotificationCenter defaultCenter] post...];
});

how can I automatically stop all background tasks when app is coming in foreground state

i am using following code
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
if ([[UIApplication sharedApplication]
respondsToSelector:#selector(beginBackgroundTaskWithExpirationHandler:)])
{
UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{}];
// Perform work that should be allowed to continue in background
[self changeCounter];
//[NSThread detachNewThreadSelector:#selector(changeCounter) toTarget:self withObject:_viewController];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}
#endif
changeCounter contains loops which may end after some time .But before ending the loop if app comes in foreground then i can see only black screen untill loop finishes.
So how can I stop all tasks as app comes in foreground
this is code for changeCounter
-(void)changeCounter{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while(back==1.0f){
NSLog(#"loop is runnig");
[NSThread sleepForTimeInterval:1.0];
if(_viewController.minutes<0){
if(_viewController.fadeSeconds>0){
float div=_viewController.deviceVolume/[_viewController.volumeData.fadeTime floatValue];
_viewController.musicPlayer.volume=_viewController.musicPlayer.volume-div;
_viewController.fadeSeconds-=1;
}
else {
[self stop];
//self.musicPlayer.volume=0.0f;
// counter=0;
NSLog(#"closing the sound");
[_viewController.musicPlayer pause];
NSLog(#"fade seconds %i minutes ",_viewController.fadeSeconds);
if(_viewController.dvol==0){
_viewController.musicPlayer.volume=deviceVolume;
_viewController.dvol=1;
}
back=0.0f;
}// end of the else
}
I think that your [self changeCounter] method is running on your application's main thread which is why your seeing the black screen until the operation is finished. You should think about whether it is appropriate for that operation to run on the main thread or whether you should move it to a background thread. Do you really want to kill the task when you come back to the foreground or is it ok for the task to complete as long as the UI is not locked up?

How to cancel a worker thread in applicationDidEnterBackground on iOS

I've heavily researched for a proper solution but it didn't work out functional:
My iPhone app updates data via NSURL requests on user demand. Each file loaded online is only 1.5k in size but one update can consist of 400 of such files. So I do the download stuff in a separate thread which is cancelable and during the update there is an UIAlertView with process indication and cancel button. The thing may run 1...3 minutes, so it can exceed the timeout the device is staying alive or other things happen and my app will go background.
When I do nothing when applicationDidEnterBackground is called, I realize that my app is suspended and also the worker thread. It wakes up and continues work when the app is in foreground again. So far so good. I get into trouble when I press the cancel button after being in foreground again - then the thread reacts as being cancelled (as it should) but immediately runs again and crashes at the end (with error codes somewhere deep in Apple frameworks). It works perfectly to cancel the thread as long as the app keeps staying in foreground - so my idea is to stop/cancel it when applicationDidEnterBackground is entered. I've tried some things to do so but each attempt ends in the fact that the worker thread is suspended at that moment applicationDidEnterBackground is called, so I can't cancel the thread and wait for that. One example I tried is this:
diagView* viewDiagCtrl = startDiag.diagViewControl;
if (viewDiagCtrl != nil && viewDiagCtrl.actionThread != nil)
{
// UIBackgroundTaskIdentifier bgTask is instance variable
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
NSLog(#"stopping actions definitely");
[viewDiagCtrl stopBackGroundActivity:self];
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
dispatch_async(dispatch_get_main_queue(),
^{
// Start with 10ms time boxes
NSTimeInterval ti = 0.01;
while ([application backgroundTimeRemaining] > 1.0)
{
if (![viewDiagCtrl.actionThread isExecuting])
break;
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:ti];
// Let the current run-loop do it's magif for one time-box.
[[NSRunLoop currentRunLoop] runMode: NSRunLoopCommonModes
beforeDate: date];
// Double the time box, for next try, max out at 1000ms.
ti = MIN(1.0, ti * 2);
}
[viewDiagCtrl stopBackGroundActivity:self];
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}
I'm not experienced with the whole queue stuff and found such a construct somewhere. I assume that all things dispatched work in the main thread and the worker thread remains suspended so that I don't have any chance to cancel it. Any ideas to come around with that?
I've also read about attempts to do everything without multithreading - but I don't really appreciate that.Is there maybe some useful link to handle the "go background" situation properly?
You can wrap your thread work in the background thread like:
...
[self performSelectorInBackground:#selector(backgroundThread)
withObject:nil];
...
-(void)backgroundThread
{
// do a download once a minute in a background thread - dont let the system suspend us
while(true)
{
BOOL expire = NO;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{expire = YES;}];
while(!downloadComplete && !expire)
{
//do your multiple file downloads here;
}
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
[NSThread sleepForTimeInterval:60];
}
}
What this will do is continue the task in progress which will continue when the app is backgrounded - I do this is several places in my heavily threaded/network intensive app.
AFAIK: You don't have to wait until the app is being backgrounded to call beginBackgroundTask - you should call it any time your app has a function it needs to complete without being interrupted/suspended.
I also use NSURLRequest with:
[request setNetworkServiceType:NSURLNetworkServiceTypeVoIP];
(and use the voip background mode in the supportedbackgroundmodes flag)

iPhone background task stops running at some point

In my iPhone app I have a background task that starts running when the app enters the background state.
The task runs fine and is structured in a loop like this:
run.
sleep for 5 min.
run.
sleep for 5 min.
etc.
For some reason, the task stops running after a certain amount of time... say half an hour to an hour.
Can anyone help me understand why?
Here is the background task code:
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"bgCheckSwitch"] == YES) {
//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 = [application 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)
{
[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.
[someClass doSomeThing]; //The actual method performed by the task. The looping is in the method.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
}
}
A background task only runs for a period of time, then the OS kills it. This is discussed in the multitasking WWDC10 videos.