I would like to know how it is possible to continue a async NSURLConnection, which has been started in the foreground, when the app will be terminated.
Currently I am starting a NSURLConnection when the app goes in the background. This works fine as long as the user is slower than the connection, when he wants to terminate the app. But when the user is quicker than it, the connection can't be established.
Here is my code:
// AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
AnObject *newObject = [[AnObject alloc] init];
[newObject InactiveApp];
}
// AnObject.m
- (void)InactiveApp
{
self.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
// setting up the NSURLRequest
// [...]
dispatch_async(dispatch_get_main_queue(), ^
{
[NSURLConnection connectionWithRequest:request delegate:self];
});
}
// delegate functions, endBackgroundTask-closing, etc. is following
Unfortunately this is not working and I would like to know whether someone knows another way to fix it. There has to be a way which is similar like Snapchat or WhatsApp is doing it, because when you write an message and terminate the app right after pressing send, the message will be delivered.
The only way I could imagine is to do it with a background fetch but I think that is not the best solution, due to the fact that I just want to make one single connection when the App is send to the background.
I agree with Andy, that you should pursue NSURLSession and a background NSURLSessionConfiguration. See downloading content in the background section of the App Programming Guide for iOS: Background Execution.
By the way, the idea in your question will work fine (especially if you need support for iOS versions prior to 7.0, where NSURLSession and its background sessions are not available). Two observations regarding your code snippet:
The way you've written it, would appear that your AnObject would be prematurely deallocated when it falls out of scope and your app would therefore fail when it tried to call the delegate methods. Make sure to maintain a strong reference to AnObject.
Don't forget to call endBackgroundTask when the download is done. Likewise (and more subtly), the timeout handler should end the background task, too. See the Executing Finite Length Task section of the aforementioned App Programming Guide.
By the way, you mention requests continuing after the app is terminated. If a user manually terminates an app, that kills both background tasks contemplated in your question as well as background NSURLSession tasks. These are intended to gracefully handle continuing tasks if the app leaves foreground, not if the user manually terminates the app. The NSURLSession approach gracefully handles terminations due to memory pressure, but not manual termination.
Related
I have a prototype single-view app which monitors download tasks.
I'm trying to deal with following use case:
Downloads are initiated via NSURLSession while app is in foreground. NSURLSession is created with background configuration.
I kill the app with "Xcode Stop", so that app continues download in background. While app is alive, I orderly receive progress callbacks from NSURLSession.
I manually start the app (not by Xcode, but tapping the launch icon), when the downloads have not been completed yet.
I don't receive any URLSession delegate calls for tasks started in previous app's life. The only thing that gets called is handleEventsForBackgroundURLSession but that's called on AppDelegate by the OS (different case than NSURLSession delegate calls).
I want to show progress of ongoing download tasks. Can this be done after app relaunch (when app was terminated by the system, not manually!)?
After app relaunch, NSURLSession is initialized with same identifier, new delegate object, so I figured delegate will continue to receive calls for session's tasks (because session identifier is the same), but apparentely that's not the case.
There is a note in Apple's documentation:
The session object keeps a strong reference to the delegate until your app explicitly invalidates the session. If you do not invalidate the session, your app leaks memory.
but I guess this only applies to case when app is alive. When the app is terminated, then all app's objects are gone.
Make sure NSURLSession is properly initialised when app launches. That's what the problem was in my case. I had TransferManager which initialised session as lazy getter which was not getting invoked...
Now that the NSURLSession is properly initialised, callbacks are fired regularly.
Stupid error, but there it is.
You goal seems to be in number 4, trying to receive URLSession delegate callbacks while in the background. I've been struggling with that myself and wasn't able to find a great solution, however I did realize that whenever I performed any actions (even simply calling the completionHandler() callback) in handleEventsForBackgroundURLSession: I received a call to
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
Seems like performing operations might wake up the delegate that was specified when creating your NSURLSession.
That is the correct method to perform any UI updates (such as showing progress) since that's the only location you'll know the background task is complete. Just make sure to call the completion handler callback after you are done!
Also this may be of some help to you: My NSURLSessionDelegate methods are not getting called during a background download
I am trying to maintain a MultipeerConnectivity "session" when the application enters temporarily in the background, so I thought about using a background task as I've seen few times here ... The problem is I have no idea how to "maintain" the session with the UIBackgroundTask, can someone please post a hint
I don't care about the advertisers/browsers, it's okay to stop them, but I'd like the session to not disconnect as reconnecting is super buggy for the moment.
As per apple documentation "If the app moves into the background, the framework stops advertising and browsing and disconnects any open sessions. Upon returning to the foreground, the framework automatically resumes advertising and browsing, but the developer must reestablish any closed sessions" Refer: Apple doc
One way of extending the connection is as follows
Answering my own question, hoping it would help people in the same situation.
For people new to iOS development, "using a background service" simple means turning on the "Background Modes" option in the "Capabilities" tab of your target.
That alone should give your app around 10 minutes life in the background before it gets killed.
But, when the app goes to background, I use the "backgroundTimeRemaining" to know how much time I have left, it just starts at 180 (in sec, so 3 minutes), yet, the printing loop did continue to work passed three minutes, which means there is a need to manually code what should happen when the time is reached.
For Multipeer Connectivity, this is enough to maintain the connection alive when the app enters background, and it will still receive all messages/streams without a problem.
For the sake of stability, I do some cleaning as follow:
In the appDelegate.h
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask; //declaring a background task
In the appDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^
{
//This is called 3 seconds before the time expires
//Here: Kill the session, advertisers, nil its delegates,
// which should correctly send a disconnect signal to other peers
// it's important if we want to be able to reconnect later,
// as the MC framework is still buggy
[application endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid; //Invalidate the background task
}];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Here: We should init back the session, start the advertising and set the delegates from scratch
// This should allow the app to reconnect to the same session with more often than not
self.backgroundTask = UIBackgroundTaskInvalid; //Here we invalidate the background task if the timer didn't end already
}
I've asked this same question once on the apple developer forums. One of the Apple employees told me that basically all of the Multipeer connectivity should be considered off-limits when your app is not int the foreground.
I use this code to execute function every X minutes:
- (void)executeEveryOneMinute
{
[self myFunction];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self executeEveryOneMinute];
});
}
And it works when app is in foreground.
But when app goes background it doesn't work anymore.
When I return app to foreground again it execute function once.
And continue to call function every minute again.
So how to make this to work in background too?
See the Background Execution and Multitasking section of the iOS App Programming Guide: App States and Multitasking for a discussion of the possibilities. You can, for example, keep the app running in the background for a few minutes in order to complete some finite length task. Or you can continue to run the app in the background for a longer period of time if it's performing one of a very particular list of functions (quoting from the aforementioned document):
Apps that play audible content to the user while in the background, such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services. Declaring the services lets the system know which services you use, but in some cases it is the system frameworks that actually prevent your application from being suspended.
But, a fundamental design principle in iOS battery/power management is that random apps can not (and should not) continue to run in the background. If you share what precisely you're trying to do (namely, what precisely you're doing inside that executeEveryOneMinute method), though, we can offer counsel on how to achieve the desired effect, if possible.
If you're trying to have an upload continue in the background, in iOS 7 and greater, you should consider using NSURLSession with a background session configuration ([NSURLSessionConfiguration backgroundSessionConfiguration:identifier]; there is a similar method in iOS 8). This will continue to attempt to upload (automatically, without further intervention on your part) not only after your app has left the foreground, but even after the app is terminated (e.g. due to memory pressure or a crash). AFNetworking offers a NSURLSession-based class, AFURLSessionManager, which supports this (though it's not NSOperation-based). This way, you enjoy background uploads, but conforms to Apple guidelines on background operation, notably with less dramatic battery impact than retrying yourself every 60 seconds.
I'd suggest you refer to the latter part of WWDC 2013 video What’s New in Foundation Networking, which demonstrates this process (they're doing a download, but the idea is the same for uploads).
Timer works on Main thread. When application goes into background, its timers become invalid. So, you cant do the same when application goes into background.
You can't do this with help of timer as it will be invalidated in background. You can try check this.
You should use background tasks to achieve what you want
UIApplication* app = [UIApplication sharedApplication];
task = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:task];
task = 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.
NSLog(#"Started background task timeremaining = %f", [app backgroundTimeRemaining]);
if (connectedToNetwork) {
// do work son...
}
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
});
I'm creating an online-shop-style app where users can browse different products on their iPad and order these products. The ordering process consists of creating an xml-file with the user's data and the relevant products he would like to order. But sometimes there might be the case, that users don't have an internet connection right now and I would like to create some mechanism, which checks every x minutes for an active internet connection and then tries to deliver the order-xml. It should repeat this step until it gets connected to the web and then just stop it, when all offline carts have been sent.
I have already been searching the web but only found ways to do this on iOS 7 (with UIBackgroundModes - fetch). But I don't want to use iOS 7 because the app is already done and I'm not planning to redesign it for iOS 7 (it's an Enterprise App). As far as I know, the current Background Execution time on iOS 6 is limited to something like 15 minutes, is that correct?
Any ideas on how to solve that?
Thanks.
EDIT:
I have tried the following in - (void)applicationDidEnterBackground:(UIApplication *)application
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
[[InstanceHolder getInstance] startNetworkTimer];
}];
and here is what should happen next:
- (void) startNetworkTimer{
if ([CommonCode getAllOfflineCartsForClient:nil].count > 0){
NSTimer *pauseTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(offlineCartLoop:) userInfo:nil repeats:YES];
}
}
- (void) offlineCartLoop:(id)sender{
if([CommonCode isInternetConnectionAvailable]){
[self sendOfflineCarts];
[sender invalidate];
}
}
startNetworkTimer gets called as it should, but then it doesn't call the offlineCartLoop function :-(
EDIT 2:
I think the timer-thing was the problem. I'm now calling the offlineCartLoop function like this:
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
[[InstanceHolder getInstance] offlineCartLoop:nil];
}];
and changed the offlineCartLoop function to this:
- (void) offlineCartLoop:(id)sender{
if([CommonCode isInternetConnectionAvailable]){
[self sendOfflineCarts];
}else{
[NSThread sleepForTimeInterval:10.0];
[self offlineCartLoop:nil];
}
}
Seems to work, but will this run forever? Is there anything else I need to take care of?
There is no solution to what you want - there is no such thing as being able to periodically check every N minutes in the background unless it is within the time window granted by beginBackgroundTaskWithExpirationHandler.
However that only permits 10 minutes of execution time for iOS6 and earlier, or approximately 3 minutes for iOS7.
You cannot cheat and try and use a background mode if your app does not need it, and even the background modes do not permit you to freely run whenever you want.
Even the new background modes in iOS 7 do not permit you to run on a scheduled basis.
Your best best actually is iOS7 even though you don't want to migrate to iOS7 - the background fetch being the relevant mode (even though you are pushing not fetching). With that background mode you will be able to have the opportunity to execute but not when you decide, only when the OS decides - and the frequency of that depends upon how the user uses your app.
With iOS6 your options are even less restricted.
See iOS: Keep an app running like a service
Basically there just is no such thing as continuous background execution, nor periodic background execution, nor the app deciding when it wants to run when in the background.
If the user does not have an internet connection at the time they use your app to place the order then you should be notifying them of that anyway (if you don't then your app risks rejection from the app store) and maybe tell them to try again later.
If they are in flight mode the user will know they are in flight mode, if there is a temporary interruption (such as the phone is in an elevator or tunnel) then your app could keep on trying for as long as it is able - keep trying every minute while in the foreground, then when you switch to the background you know you have 10 minutes left, keep trying until the 10 minutes has nearly expired then post a local notification to the user notifying them that the app was unable to place the order due to lack of connectivity. If the user clicks on the notification and your app launches then the app will have the chance to retry again at that point.
If you still cannot make a connection then so be it, but you will have the chance to start the retry algorithm again. But at least you have notified the user their order has not gone through.
If what you need to know is if and when a data connection is available, I recommend inverting the process: rather then querying for a data connection, let your app be notified when a data connection is available. It's more efficient.
On this subject, I suggest using Reachability: you can make a call to know if a specific URL is accessible, and execute a block of code as soon as a connection is available.
Reachability *reach = [Reachability reacabilityWithHostName:#"www.myservice.com"];
...
reach.reachableBlock = ^(Reachability *reach) {
// Process the requests queue
// You should implement the method below
[self processQueue];
}
...
if ([reach isReachable]) {
// Upload the XML file to the server
// You should implement the method below
[self uploadToServer:myRequest];
} else {
// Enqueue your request somewhere, for example into an NSArray
// You should implement the method below
[self addToQueue:myRequest];
}
The above code is meant to be a showcase (it doesn't work as is), use it as reference. I can just say that the reach variable should be a class property or data member, and that it should be initialized once.
Also, if you enqueue your requests into an NSArray, be sure to do it in thread safe mode
Alternatively, Reachability can also notify via NSNotification when a connection is available - a different way to achieve the same result. Up to you to decide which one better fits with your needs.
I have a voip app and it needs to run in the background. To my understanding these are the things I need to do:
Flag the app as voip.
Set the 'application does not run in background' flag to NO.
Set an expiration handler, a piece of code that extends the standard 10 minutes of execution time you get.
More?
I set both flags in the info.plist file and I get my 10 minutes. I tried what is suggested in this post. Here is my code:
//in didFinishLaunchingWithOptions:
expirationHandler = ^{
NSLog(#"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
NSLog(#"restarting background task");
bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
NSLog(#"finished running background task");
};
//in applicationDidEnterBackground
NSLog(#"entering background mode");
bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// inform others to stop tasks, if you like
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyApplicationEntersBackground" object:self];
//this while loop is just here for testing
inBackground = true;
while (inBackground) {
NSLog(#"stayin alive!!"); //this keeps going forever
sleep(10);
}
});
The situation:
I use a third party library that handles the communication with our webservice. The service is a CommuniGate pro server. I receive presence updates (online/offline) and instant messages from contacts via the library. The library is CommuniGate's ximss library, a protocol they made which is similar to xmpp and is used for xml-based sip requests, as well as IM and presence. When the user logs in to the app, he sees his contacts (CmmuniGate friends list) and he can choose to call one. After a ximss verification message has been sent and the other side accepted the call it logs the start time of the call and starts a facetime call.
The problem:
When the app enters the background by pressing the home button, I start seeing the 'stayin alive' message in the log and every ten minutes I see that it restarts the background task.
When the app enters the background by pressing the power button, the 'staying alive' messages start showing up for ten minutes, after that it restarts the background task and start restarting it about every 50-100 miliseconds.
I would've been fine with this for now, even it eats battery, because I have time to work on updates and our users don't own the ipads, we do. The problem for me now is that the ximss library loses it's connection (it is session-based). I could restart the session in the library, but this means quite a bit of data transfer to fetch the contacts list and some users use 3g.
I can't edit the library's source, nor can I see it, so I don't know if it creates the sockets the right way.
What do I have to do to handle both situations correctly? I don't even understand why there is a difference.
You cannot re-extend background tasks like this; your app is likely to be terminated. If this is working, it's because you have the background voip mode enabled, not because you are restarting the background task.
Once you have set the voip plist entry, iOS will attempt to keep your app alive as long as possible and restart it if it does get terminated. From Implementing a VoIP App:
Including the voip value in the UIBackgroundModes key lets the system
know that it should allow the app to run in the background as needed
to manage its network sockets. An app with this key is also relaunched
in the background immediately after system boot to ensure that the
VoIP services are always available.
In addition to setting this key, if you need to periodically run code to keep your voip connection alive, you can use the setKeepAliveTimeout:handler: method on UIApplication.
See also Tips for Developing a VoIP App:
There are several requirements for implementing a VoIP app:
Add the UIBackgroundModes key to your app’s Info.plist file. Set the value of this key to an array that includes the voip string.
Configure one of the app’s sockets for VoIP usage.
Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be
executed periodically. Your app can use this handler to maintain its
service connection.
Configure your audio session to handle transitions to and from active use.
To ensure a better user experience on iPhone, use the Core Telephony framework to adjust your behavior in relation to cell-based
phone calls; see Core Telephony Framework Reference.
To ensure good performance for your VoIP app, use the System Configuration framework to detect network changes and allow your app
to sleep as much as possible.
Almost all of the documentation you need is on the Apple developer site.