this is how I start a background task when application goes background ,
void applicationDidEnterBackground:(UIApplication *)application
{
btId = UIBackgroundTaskInvalid;
UIApplication* cuiApplication = [UIApplication sharedApplication];
void (^backgroundTimeRemainingExtenderHandler)() = ^() {
NSTimeInterval timeRemaining1 = [cuiApplication backgroundTimeRemaining];
if(btId != UIBackgroundTaskInvalid){
[proximityEngine StopEngine];
[cuiApplication endBackgroundTask:btId];
btId = UIBackgroundTaskInvalid;
}
};
btId= [cuiApplication beginBackgroundTaskWithExpirationHandler:backgroundTimeRemainingExtenderHandler];
if(bgmanager != nil){
[bgmanager BeginBackgroundTaskMainLoop];
}
}
My problem is that when my background task calls :
NSURLConnection sendSynchronousRequest
The expiration block is being called even though there is more time remaning , how can I prevent this ?
Regards ,
James
EDIT :
After reading the answer below : I still have 596 seconds left when querying the amount of time left and yet still IOS calls the expiration block handler.
beginBackgroundTaskWithExpirationHandler: is the means by which apps request a little extra background time to do some tidying up as a result of going into the background. However iOS reserves the right to decide how much time it will offer you, if any at all, and to kill your process if you fail to end within the required amount of time.
You don't get to execute in the background indefinitely and you don't get to pick your own time limit. You can query what you've been allocated via backgroundTimeRemaining but that's pretty much the full extent.
Per the documentation the handler is called "shortly before the application’s remaining background time reaches 0". So you should expect backgroundTimeRemaining not quite to be zero.
That being said, if your URL connection hasn't yet completed then you're just meant to note somewhere that it didn't complete and deal with the error next time you come back from the background, usually by trying again. That's what your expiration handler should do, and it needs to do it fast.
The extra time allotted to your app is non-negotiable however.
In my particular case - which I do not quite sure why it behaved the way it behaved , I performed the task on a different thread than the beginbackgroundtask thread , after sendsync returned in that thread the backgroundtask was interrupted by the OS .
When calling sendsync in the beginbackground original thread , it does not occur.
Not sure if its something logical and I did something wrong or an OS bug.
Related
I'm trying to complete a task in the background using UIApplication.shared.beginBackgroundTask but for some reason the expirationHandler is never called. The task I'm trying to complete is a video export from photo library but sometimes the export cannot be completed in time while the user is using the app in the foreground.
This is the code I'm using :
func applicationDidEnterBackground(_ application: UIApplication) {
if backgroundTask == .invalid && UploadQueue.instance.hasMoreWork() {
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "ExportQueue") {
NSLog("DriveLog - System is requesting end. Still more work to do ...")
self.endBackgroundTask()
}
print("Invalid? \(backgroundTask == .invalid)")
NSLog("DriveLog - Starting background task: %i", backgroundTask.rawValue)
}
}
func endBackgroundTask() {
NSLog("DriveLog - End called")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
I'm also calling :
(UIApplication.shared.delegate as! AppDelegate).endBackgroundTask()
during my task if I finish it earlier.
However I never see my expirationHandler being called in the log.
I have also tried putting beginBackgroundTask when starting the task in foreground but I get a warning message about task expiration while being in foreground.
You have not understood what the expiration handler is. It is called only if your time expires. Hence the name.
As soon as you call begin, start your task in the next line (not in the expiration handler). And when you are finished, call end.
You thus need to end the background task in two places: in the expiration handler, and outside it after actually performing your task.
It is very important to call end in both places, because if you fail to do so, the system will decide that you are a bad citizen and will never grant you any extra background time at all.
So, this is the diagram of the flow you need to construct:
Also note that this has nothing to do with UIBackgroundModes. That's a totally different mechanism.
matt's answer covers everything. I'm just going to try to give the same answer in different words because your edit suggests that matt's answer wasn't clear to you. (Read it again, though, it really does cover everything I'm going to say here, just in different words.)
You should not call beginBackgroundTask in applicationDidEnterBackground. You call it when you start whatever task you want time for. In your example that's going to be somewhere inside of UploadQueue. You don't call beginBackgroundTask when going into the background. You call it when you're starting a task that you would like to finish even if you go into the background.
Background tasks generally do not belong to the UIAppDelegate. They belong to the thing that creates the task (in your case: UploadQueue). You can create all the background tasks you want. They cost almost nothing. It's not just one "I want background" at the app level. Read matt's flow chart closely.
It's unclear from your question why you expect the expiration handler to be called. Do you expect your task to task to take so long that the OS forces you to stop it? That's what the expiration handler is for. If you've built your system correctly, it should rarely be called. Your task should end long before it's expired.
For full docs on how to do this, see Extending Your App's Background Execution Time. In particular note the caution:
Don’t wait until your app moves to the background to call the beginBackgroundTask(withName:expirationHandler:) method. Call the method before performing any long-running task.
I am working on an iOS application that, say on a button click, launches several threads, each executing a piece of Open GL code. These threads either have a different EAGLContext set on them, or if they use same EAGLContext, then they are synchronised (i.e. 2 threads don't set same EAGLContext in parallel).
Now suppose the app goes into background. As per Apple's documentation, we should stop all the OpenGL calls in applicationWillResignActive: callback so that by the time applicationDidEnterBackground: is called, no further GL calls are made.
I am using dispatch_queues to create background threads. For e.g.:
__block Byte* renderedData; // some memory already allocated
dispatch_sync(glProcessingQueue, ^{
[EAGLContext setCurrentContext:_eaglContext];
glViewPort(...)
glBindFramebuffer(...)
glClear(...)
glDrawArrays(...)
glReadPixels(...) // read in renderedData
}
use renderedData for something else
My question is - how to handle applicationWillResignActive: so that any such background GL calls can be not just stopped, but also be able to resume on applicationDidBecomeActive:? Should I wait for currently running blocks to finish before returning from applicationWillResignActive:? Or should I just suspend glProcessingQueue and return?
I have also read that similar is the case when app is interrupted in other ways, like displaying an alert, a phone call, etc.
I can have multiple such threads at any point of time, invoked by possibly multiple ViewControllers, so I am looking for some scalable solution or design pattern.
The way I see it you need to either pause a thread or kill it.
If you kill it you need to ensure all resources are released which means again calling openGL most likely. In this case it might actually be better to simply wait for the block to finish execution. This means the block must not take too long to finish which is impossible to guarantee and since you have multiple contexts and threads this may realistically present an issue.
So pausing seems better. I am not sure if there is a direct API to pause a thread but you can make it wait. Maybe a s system similar to this one can help.
The linked example seems to handle exactly what you would want; it already checks the current thread and locks that one. I guess you could pack that into some tool as a static method or a C function and wherever you are confident you can pause the thread you would simply do something like:
dispatch_sync(glProcessingQueue, ^{
[EAGLContext setCurrentContext:_eaglContext];
[ThreadManager pauseCurrentThreadIfNeeded];
glViewPort(...)
glBindFramebuffer(...)
[ThreadManager pauseCurrentThreadIfNeeded];
glClear(...)
glDrawArrays(...)
glReadPixels(...) // read in renderedData
[ThreadManager pauseCurrentThreadIfNeeded];
}
You might still have an issue with main thread if it is used. You might want to skip pause on that one otherwise your system may simply never wake up again (not sure though, try it).
So now you are look at interface of your ThreadManager to be something like:
+ (void)pause {
__threadsPaused = YES;
}
+ (void)resume {
__threadsPaused = NO;
}
+ (void)pauseCurrentThreadIfNeeded {
if(__threadsPaused) {
// TODO: insert code for locking until __threadsPaused becomes false
}
}
Let us know what you find out.
I've been able to reproduce a defect in our app twice, but most times I fail. So I'm trying to understand what could possibly be going on here and hopefully have some new things to try. Our app times out and logs the user out after 10 minutes using an NSTimer. Every time the screen is touched the timer is reset, this all works great.
When the user backgrounds the app and comes back, the following code gets called:
- (BOOL)sessionShouldTimeOut {
if (self.timeoutManager) {
NSTimeInterval timeIntervalSinceNow = [self.timeoutManager.updateTimer.fireDate timeIntervalSinceDate:[NSDate date]];
if (timeIntervalSinceNow < 0) {
return YES;
} else {
return NO;
}
}
return NO;
}
- (void)timeoutIfSessionShouldTimeOut {
if ([self sessionShouldTimeOut]) {
[self.timeoutManager sendNotificationForTimeout];
}
}
This (I suspect) is the code that's failing. What happens when it fails is the user logs in, hits the home page and locks their phone. After 10+ minutes, they unlock and the app isn't logged out. When they come back, it's the code above that gets executed to log the user out, but in some scenarios it fails - leaving the user still on the homepage when they shouldn't be.
Here's my current theories I'm trying to test:
The timer somehow fires in the background, which then runs the logout routine, but since we're in the background the UI isn't updated but the timer is invalidated (we invalidate the timer after logout) I'm not sure if UI code called from the background will be shown after the app is in the foreground, so this may not be a possibility.
The user actually is coming back a few seconds before the timer fires, then after a few seconds when it should have fired it doesn't since it was backgrounded for 10 minutes. Do timers continue to hit their original fire time if the app goes to the background?
Somehow, while in the background, self.timeoutManager, updateTimer, or fireDate are being released and set to nil, causing the sessionShouldTimeOut method to return NO. Can variables be nilled in the background? What would cause them to if they could be?
The logout routine gets run while the phone is taking a while to actually move to the app, potentially causing the UI updates to not be reflected?
I'm very open to other theories, as you can see a lot of mine are very very edge case since I'm not sure at all what's happening.
I'd appreciate any guidance anyone can offer as to what else I may be able to try, or even any insights into the underworkings of NSTimer or NSRunLoop that may be helpful in this scenario (the documentation on those is terrible for the questions I have)
In AppDelegate.h set
applicationDidEnterBackground:
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
This tells the os that you still have things going and not to stop it.
In my app using iOS 9.2, Swift 2.1 I need to save some data into core data when the app goes to background. For this I registered each of the view controllers in the call path for UIApplicationDidEnterBackgroundNotification notification, with an instance method each for saving respective data.
I read on multiple places that by default the app gets about 5 seconds to finish off the execution and hence we need to use beginBackgroundTaskWithExpirationHandler to extend it to about 5 minutes. Following is an example of the selector method that responds to the above notification.
func applicationEntersBackground()
{
print("Before Extension: \(UIApplication.sharedApplication().backgroundTimeRemaining)")
let taskID = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
print("During Extension: \(UIApplication.sharedApplication().backgroundTimeRemaining)")
saveCoreData()
if(taskID != UIBackgroundTaskInvalid)
{
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
print("After Extension: \(UIApplication.sharedApplication().backgroundTimeRemaining)")
}
Following is the results of print() statements
Before Extension: 179.933103708318
During Extension: 179.930266333336
After Extension: 179.922843541659
My doubts are
Why is the remaining time about 180 seconds even before I requested for time extension? I tried multiple times. It is always close to 180 seconds and not the 5 seconds as suggested.
Why doesn't the call to beginBackgroundTaskWithExpirationHandler have any impact on the remaining time?
Once the applicationEntersBackground method of a VC returns, similar notification is sent to another VC's corresponding method. Suppose 180 seconds is the total extended duration and VC1 spends about 10 seconds on notification handling, does VC2 notification handler get around 170 seconds between its beginBackgroundTaskWithExpirationHandler - endBackgroundTask calls?
Between successive invocations of the notification handlers of different VCs, there is obviously a very short period where the extension request is not active. How does the timing play out in this case? Does the 5 second counter (provided it is true) come back to life as soon as an endBackgroundTask call is made, and possibly terminate the application before the next VC can get its notification?
Appreciate any help.
By looking at the documentation for backgroundTimeRemaining:
While the app is running in the foreground, the value in this property remains suitably large. If the app starts one or more long-running tasks using the beginBackgroundTaskWithExpirationHandler: method and then transitions to the background, the value of this property is adjusted to reflect the amount of time the app has left to run.
To answer your questions:
backgroundTimeRemaining stays around 180 while the application is in foreground so you can tell what time you'd have once you start a background task. This value is not an indicator of how long are you allowed to run without a background task.
beginBackgroundTaskWithExpirationHandler has an impact, as you can see, the remaining time decreased (by a small value as the method doesn't take much time)
What matters here is the time passed between the call to beginBackgroundTaskWithExpirationHandler and the one to endBackgroundTask. You can split whatever you need the time interval between your calls, providing you don't exceed the 180s limit
Once you call endBackgroundTask the application will be suspended, regardless it took 2 seconds or 179 seconds.
You can find out more details about application entering background here. I'd recommend going through the documentation, it might clarify other questions you might have on this matter.
I have a view with a bunch of UISwitch.
My problem is that when I tap on a switch I need to wait about 10 seconds before being able to tap any other switch of the view.
Here is my code :
-(void) didTapSwitch:(UISwitch *)sender
{
NSLog(#"BEGIN didTapSwitch, %#",sender);
DADudesManager *dudesManager = [DADudesManager getInstance];
DADude *updatedDude = [dudesManager.dudesList objectAtIndex:[[self.spendingDudesTableView indexPathForCell:sender.superview.superview] row]];
DAAccountManager *accountManager = [DAAccountManager getInstance];
[accountManager.accountsOperationQueue addOperationWithBlock:^{
NSLog(#"BACKGROUND OPERATION BEGINS switchDudeBeneficiates, %#",sender);
DASpendingsManager *spendingsManager = [DASpendingsManager getInstance];
[[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{updatedDude.dudeName: [sender isOn] ? #"1" : #"0"}];
NSLog(#"BACKGROUND OPERATION ENDS switchDudeBeneficiates, %#",sender);
}];
NSLog(#"END switchDudeBeneficiates, %#",sender);
}
My spendingObserver is a Firebase object initiated before.
When the code above is executed, the NSLogs show almost instantaneously in the console, the data is updated online at the same time, but the switches don't react to any tap for another 9 to 11 secs.
Of course commenting the line [[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{weakDude.dudeName: [weakSwitch isOn] ? #"1" : #"0"}]; removes the latency, so the problem must come from Firebase, but I have no clue what's going on.
I am probably missing something obvious as I'm pretty new to IOS development !
I can think couple of reasons.
You are sending the PayLoad in the main thread, which is causing the User INterface events to be suspended.
The code you ran, might be linked to other functions in the library you are using, that might be causing the lag.
TRY - >
try putting your code in an NSOperation and execute that. Or use GCD to do work on different thread just not the UI thread which is the main thead.
Step back and simplify. Make your switch code simply log the change in value. NSLog includes a timestamp, so you can tell when the switch events occur.
If do-nothing code responds quickly, as I suspect it will, then add log statements at the beginning and end of your switch action method. That way you can see if there is a delay between the beginning and end of the processing.
You could also run the app in instruments (time profiler) and see where your app is spending time.