why beginBackgroundTaskWithExpirationHandler ending early? - ios

I am trying to get this to work correctly but it always seems to end early at 174 seconds (2.9 mins). I been following every tutorial possible online on how to use beginBackgroundTaskWithExpirationHandler and I don't see anything wrong with my code. I need this to end at 8.5 mins. The endBackgroundTask method doesn't even gets call before the expiration handler gets called. Is anything wrong with this?
- (void)applicationDidEnterBackground:(UIApplication *)application {
if([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)])
{
NSLog(#"Multitasking Supported");
backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^ {
NSLog(#"Terminated");
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}];
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(count:)
userInfo:nil
repeats:YES];
}
else
{
NSLog(#"Multitasking Not Supported");
}
}
-(void)turnOffDesktops:(NSTimer *)time {
//do smth
if(count < (60*8.5)){
NSLog(#"%d",count);
count++;
}else{
[application endBackgroundTask: backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
[timer invalidate];
timer = nil;
count = 0;
}
}

There has never been a commitment from Apple on how long you would be allowed to perform your background tasks. Historically (until iOS7), apps were given usually 10 minutes to run in the background. This is no longer the case! Watch the WWDC 2013 video on backgrounding. With the addition of the new download & upload API in NSURLSession (ability to schedule download and upload tasks on an external dedicated daemon), Apple has reduced the allowed background time significantly. They've done this because this API has always been meant for download and upload, not arbitrary tasks in the background.
You can determine the amount of time left in background by querying - [UIApplication backgroundTimeRemaining]. You can use this to schedule your code to start at the latest possible.

Related

Call RESTful API while application in background state for indefinite time

I want to call a post method every 60 seconds later while application is in background mode.My objective is to store user location and send it to my server.So far, for test purpose I have used beginBackgroundTaskWithExpirationHandler and NSTime for every 3 seconds call of a temporary post call and it's working fine. But problem is, this background task is being stopped after 10 times call. but I want it to call it indefinite times in certain interval(suppose every 60s or once in a day).
Application iOS Deployment target : 9.0 and its Objective C project with swift compatibility. So far I have done this:
- (void)applicationWillResignActive:(UIApplication *)application {
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self endBackgroundUpdateTask];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[self targetMethod];
}];
}
can anyone help me out how can I call this targetMethod in regular interval?
Thanks

Why does NSTimer persist firing while app is suspended?

As far i know, there are two distinct stages of iOS application lifecycle when it is hidden:
Background - when we are still able to proceed computation and run code;
Suspended - when any computation is freezed and we can't do anything till the user returns to the app.
So i'm a bit confused about a following example:
- (void)applicationDidEnterBackground:(UIApplication *)application {
(void)(^expirationHandler)() = [^{
[application endBackgroundTask:bgTask];
self.bgTask = UIBackgroundTaskInvalid;
} copy];
self.bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:expirationHandler];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(count)
userInfo:nil
repeats:YES];
expirationHandler();
}
- (void)count {
int i = 0;
while (i < 10000000) {
i++;
}
NSLog(#"%d", i);
}
P.S. I've reproduced it from memory, let me know if something wrong, but question is not related to correctness of the snippet.
I tested it on iPhone 6+ with iOS 9.3 and the specified timer persists firing, furthermore count method executes code (you can check it by i value). Am i right that app must proceed to suspended state after the timer scheduled? If so, why is count able to execute code?
Thanks in advance.
As per #Paulw11 in comments,
Are you testing while running under the debugger in Xcode? If so, then you app doesn't have background time limits or get suspended
I haven't found any proofs, but through empirical enquiry it appears to be true.

Background mode with NSTimers and server calls

I'm working on an sdk that uses NSTimer to make a server call when it expires at x mins and the server call resets the timer back to x mins after doing some work. The problem I'm having is that my timer stops running when my app is in the background and resumes immediately when my app is back in the foreground. How do I get this to work?
//initialize self.myTimer somewhere in my code
//called once somewhere in my code-->[self resetTimer:self.myTimer expiry:30];
- (void)resetTimer:(NSTimer *)timer expiry:(float)seconds {
[timer invalidate];
NSTimer *newTimer = [NSTimer timerWithTimeInterval:seconds
target:self
selector:#selector(updateTimer:)
userInfo:nil
repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:newTimer forMode:NSRunLoopCommonModes];
self.myTimer = newTimer;
}
- (void) updateTimer:(NSTimer *)timer {
[timer invalidate];
dispatch_queue_t queue = dispatch_queue_create("update", NULL);
dispatch_async(queue, ^{
[self serverCall];
});
}
-(void)serverCall{
//make server call and do some work
[self resetTimer:self.myTimer expiry:30];
}
The timer won't continue to fire when the app is in the background. If you're willing to let the OS set the pace of the server calls, you can accomplish this by consulting the section "Fetching Small Amounts of Content Opportunistically" in this background execution doc.
The gist is to set your app's UIBackgroundModes key == fetch in info.plist.
When iOS decides to grant your app some cycles, you can run a short task in:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[self serverCall]; // BUT! read on
}
But you'll need to refactor your serverCall to tell the caller when its done. Otherwise, you won't know when to invoke the completionHandler:.
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[self newServerCallWithCompletion:^(BOOL gotNewData) {
UIBackgroundFetchResult result = (gotNewData)? UIBackgroundFetchResultNewData : UIBackgroundFetchResultNoData;
completionHandler(result);
}];
}
See the docs for more options on UIBackgroundFetchResult.
With this, iOS will set the pace of requests, so you won't be able to make them more often than when you're app is given the chance. By persisting the time of the last request, you can make requests less often. Just check if the interval since the last request is <= to some desired max frequency. If it is, just call the completionHandler right away with "no data".

autolocking ios device, NSTimer and scheduledTimerWithTimeInterval

I would like to trigger action after some ammount of time (in production that will be 30 minutes) and right now I'm using NSTimers scheduledTimerWithTimeInterval. During tests (with timeout being 20 seconds, not 1800 seconds) everything seems fine. In debbuging mode (running from XCode) everything seems fine as well, because the device does not autolock. But in real life, when application is ran on device, autolocking (precisely autolocking, triggering lock button does not) "freezes" the timer (or at least moves the timer trigger somehow to future).
Can I handle that situation? Of course I can disable idleTimer in UIApplication sharedApplication but when application will enter background mode ipad still can autolock.
Actually you can,
First of all, create a default value for "startTimeOfMyExoticTimer" to NSUserDefaults:
[[[NSUserDefaults] standardDefaults] setObject:[NSDate date] forKey:#"startTimeOfMyExoticTimer"];
Then kick of a timer to check if the valid time range is over:
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(checkIfTimeIsOver) userInfo:nil repeats:YES];
Then implement your checker method:
-(void)checkIfTimeIsOver{
NSDate * now = [NSDate date];
NSDate * startTime = [[NSUserDefaults standardDefaults] objectForKey:#"startTimeOfMyExoticTimer"];
// Here compare your now and startTime objects. (subsract them) and see the difference between them. If your time range is to be set on 30 seconds. Then you should check if the time difference between those objects are bigger than 30 seconds.
}
This will be working even if the device is locked, the app is in background etc.
This approach worked for me, hope it'll work for you too
//Implement this block help your application run background about 10 minutes
#interface AppDeledate(){
UIBackgroundTaskIdentifier backgroundTaskId;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// if missing interval exist > start timer again and set missing interval to 0
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Did Enter Background");
backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background Task Expired !!!\n");
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
//save missing interval to NSUserDefault
}];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"Will Enter Foreground");
if (backgroundTaskId && backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}

Keep a dispatch queue running for 5 sec after moving to the background

I am using a serial dispatch queue to serialize some network requests when the user moves the app to the background.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
dispatch_queue_t opQ = dispatch_queue_create("com.myapp.network", NULL);
dispatch_async(opQ, ^{
[self sendNetworkData1];
[self sendNetworkData2];
[self sendNetworkData3];
});
}
The problem is that when they run on this queue I have created, the app doesn't stay active even for the 5 seconds it is supposed to.
On the contrary, when I send the same requests outside of a queue, they are being sent for approximately 8 sec. but the app crashes afterwards.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self sendNetworkData1];
[self sendNetworkData2];
[self sendNetworkData3];
}
I would also like to write the remaining ones on the disk, so that they can be sent the next time the user opens the app.
What's the best way to implement this?
When the application enters the background if it requires additional time to complete some task you will want to notify the OS of that. The detailed documentation is here: http://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html. Here's a quick and dirty patch.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
__block UIBackgroundTaskIdentifier backgroundTask; //Create a task object
backgroundTask = [application beginBackgroundTaskWithExpirationHandler: ^ {
[application endBackgroundTask:background_task];
backgroundTask = UIBackgroundTaskInvalid; //Set the task to be invalid
}];
dispatch_queue_t opQ = dispatch_queue_create("com.myapp.network", NULL);
dispatch_async(opQ, ^{
[self sendNetworkData1];
[self sendNetworkData2];
[self sendNetworkData3];
[application endBackgroundTask:background_task];
backgroundTask = UIBackgroundTaskInvalid; //Set the task to be invalid
});
}
The bottom line is that you notify that the application needs to run in the background with beginBackgroundTaskWithExpirationHandler: THen when your done you call endBackgroundTask: to notify the OS that you are finished processing in the background. And finally make sure that you reset the backgroundTask variable to UIBackgroundTaskInvalid.

Resources