UI-updates with Background tasks on iOS - iphone

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...];
});

Related

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

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];
}
});

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);
}
}

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)

how to stop performing selector in background?

I have some class A. In this class i have a method,
which calls [self performSelectorInBackground:...]. And it starts downloading
some info from internet.
After i tap Home button, then enter the app again, this background method keeps working.
So, if i call this method again, i have bad_access, because background method is already working and i call it twice.
Can i stop performing selector in background of the class A? For example in my applicationDidEnterBackground?
Or can i check, if selector is performing or something?
I found couple things like
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget:a];
[NSObject cancelPreviousPerformRequestsWithTarget:a selector:#selector(startDownload) object:nil];
But they didn't work for me.
So
my objAppDelegate:
#inteface ObjAppDelegate
{
A *a;
}
#implementation ObjAppDelegate
{
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//or it can be didBecomeActive..
//here. check if background task of class A is running, or just stop it ??
}
}
#implementation A
{
//some timer, or event, etc.
-(void)startDownload
{
[self performSelectorInBackground:#selector(runBackgroundTask) withObject:nil];
}
-(void)runBackgroundTask
{
//some network stuff..
}
}
i did it like this:
threadForDownload = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[threadForDownload start];
[self performSelector:#selector(startDownload) onThread:threadForDownload withObject:nil waitUntilDone:NO];
(void)threadMain:(id)data {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (YES) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}
In my startDownload method i look at activity indicator to check, whether
startDownload is already running..
-(void)startDownload
{
if (![[UIApplication sharedApplication] isNetworkActivityIndicatorVisible]) // flag..
{
//....
}
}
// I make visible networkActivityIndicator every time i start downloading
You can easily create a BOOL instance variable to determine whether background task is active.
BOOL isBackgroundTaskRunning;
Then in runBackgroundTask
if (isBackgroundTaskRunning) {
// already running
return;
}
isBackgroundTaskRunning = TRUE;
...
isBackgroundTaskRunning = FALSE;
Here's what to do:
the background task saves its thread to a property somewhere using NSThread currentThread
the background task periodically checks the thread's isCancelled property.
the main thread sends cancel to the thread object saved by the background thread in step 1.
On exit, the background thread sets the property to nil.
All of the operations on the property used to store the thread in have to be protected by #synchronized or equivalent to prevent the main thread from sending cancel to a deallocated thread object.
The background thread can't do IO operations that block for more than a short period of time. In particular, synchronous downloading of URLs using NSURLConnection is out. If you are using NSURLConnection, you'll want to move to the asynchronous methods and a run loop (arguably, in that case, you can do away with the background thread altogether). If you are using POSIX level IO, use poll() with a timeout.
I don't think that it would be save to force the interruption of a method. What you can do is to change the state of your object and check that state inside your method implementation to early return in case of a cancel (but don't forget to release allocated objects).
This is how NSOperationQueue works. From the documentation:
Cancelling an operation does not immediately force it to stop what it is doing. Although respecting the value returned by the isCancelled is expected of all operations, your code must explicitly check the value returned by this method and abort as needed.
Run the method in a background thread, and keep a record of the NSThread. Then later, you can just end the thread.