I found out that iOS can run processes even when your application is in background mode (minimized) in at most 10 minutes, and to work around that and make it run forever, it would be necessary:
Apps that need to download and process new content regularly
And that's what using NSURLSession API to make requests to my server:
-(void)SendFiles{
...
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil){
[self performSelector:#selector(SendFiles) withObject:nil afterDelay:3600.0];
}
}];
[dataTask resume];
}
Some of us know that to run processes even when the app is minimized, we should use some tags, but if you are using the API NSURLSession is no longer needed this kind of thing, right?
In the case of my project, he needs to make such a request every 1 hour on my server, and for this I added a code within the block completionHandler: to be called every 1 hour the same method that executes the send command files to the server.
My question is: The time of 10 minutes can affect the selector with delay of 3600 seconds? causing my app is fully down, and I can not send files to the server?
A couple of thoughts:
The ten minute window was reduced to three minutes in iOS 7.
You only get that window if you request it with beginBackgroundTaskWithName (or, prior to iOS 7, beginBackgroundTaskWithExpirationHandler). See the Executing Finite-Length Tasks section of the App Programming Guide for iOS.
Note, I believe that this background state behavior changes whether you run the app connected to the Xcode debugger, so make sure to test this on an actual device, and run the app directly from the device, not through Xcode.
Yes, this limited amount of time will affect your ability to call performSelector:afterDelay: with a delay of 3600.0.
If you want to have the app periodically ask the server for data, you would nowadays use "background fetch". See the Fetching Small Amounts of Content Opportunistically in the App Programming Guide. This is designed to poll a server to see if there is data to be downloaded.
You only have 30 seconds for this request to be performed. You also have no control over the timing of these requests (though whether you report whether there was data available or not may affect the timing of the subsequent request).
Related
I have been using NSURLSession to do background uploading to AWS S3. Something like this:
NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#“some.identifier"];
NSURLSession* session = [NSURLSession sessionWithConfiguration:configuration delegate:someDelegate delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionUploadTask* task = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:httpBody]];
[task resume];
In someDelegate, I have implemented didSendBodyData, didCompleteWithError and handleEventsForBackgroundURLSession.
I have three questions:
I have noticed that if I close the app while uploading is in progress, transfer will continue and successfully finish. Is handleEventsForBackgroundURLSession called when the transfer is finished while the app is closed?
Assuming that the answer to the first question is yes, how can I delete httpBody in handleEventsForBackgroundURLSession? This is a temporary file that is not needed once transfer is complete.
I would appreciate it if someone explained, in detail, how background transfer works in iOS. That is when memory is created, which callbacks are called at which states and how the app is woken up once the transfer is completed. Thanks.
When the app delegate's handleEventsForBackgroundURLSession is called, you should:
save the completion handler;
instantiate your background NSURLSession;
let all of your delegate methods to be called;
in your URLSession:task:didCompleteWithError:, you can remove those temp files; and
in URLSessionDidFinishEventsForBackgroundURLSession:, you can call that saved completion handler.
A few additional notes:
There seems to be some confusion about what happens when an app is terminated.
If the app is terminated in the course of its normal lifecycle, the URLSession daemon will keep the background requests going, finishing your uploads, and then wake up your app when it's done.
But manually force-quitting the app (e.g., double tapping on home button, swiping up on the app to force it to quit) is a completely different thing (effectively, the user is saying "stop this app and everything associated with it"). That will stop background sessions. So, yes, background sessions will continue after the app is terminated, but, no, not if the user force-quit the app.
You talk about setting breakpoints and observing this in Xcode. You should be aware that the process of being attached to Xcode will interfere with the normal app life cycle (it keeps it running in background, preventing it from being suspended or, during the normal course of events, terminating).
But when testing background session related code, it's critical to be test the handleEventsForBackgroundURLSession workflow when your app was terminated, so to that end, I'd suggest not using Xcode debugger when testing this dimension of background sessions.
I use the new OSLog unified logging system, because the macOS Console can watch what is logged by the app, while not having Xcode running at all. Then I can write code that starts some download or upload, terminates app and then watch the logging statements I have inserted in order to observe the restarting of the app in background via the macOS console. See Unified Logging and Activity Tracing video for a tutorial of how to watch iOS logs from the macOS Console.
I want to know about the behaviour of NSURLSessionDataTask in background.
Does NSURLSessionDataTask get paused right away (as soon as the app enters background)? or does iOS give some time hopefully 30 seconds or so until the response ?
No, You will not continue with NSURLSessionDataTask in background mode with defaultSessionConfiguration !
If you want to continue execution in background then you need to configure session with backgroundSessionConfiguration.
When you are using backgroundSessionConfiguration you can't send data directly to server, you have to give file url and from that url you have to send bytes or chunks to server, and you have to use uploadTask or downloadTask with backgroundSessionConfiguration!!
If you wants little time after entering in background then you can use UIBackgroundTaskIdentifier. These are very huge concepts to explain every thing here, It's better that you read documentations from apple or some tutorial for that!!! You should refer Apple Documentation for Background Execution and Apple documentation for NSURLSession !
My code is the following:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *theURLString = #"http://website.com/musicFile";
NSData *theData = [NSData dataWithContentsOfURL:[NSURL URLWithString:theURLString]];
}
There is nothing special at all. I am not even using the background thread.
Here is the behavior I get on iOS 8.x (and the behavior that I expect to get):
So, NSData is fully released and all of the occupied memory is back.
However, iOS 9.x surprised me a lot:
My questions are:
Approximately 100 MB are gone for nothing in iOS 9.x. How can I get them back? Are there any workarounds?
iOS 8.x has occupied 136.2 MB at max, while iOS 9.x used 225.9 MB at max. Why is this happening?
What is going on in iOS 9.x?
UPDATE #1:
I have also tried using NSURLSession 'dataTaskWithURL:completionHandler:' (thanks to #CouchDeveloper). This reduces the leak, but doesn't fully solve the problem (this time both iOS 8.x and iOS 9.x).
I used the code below:
NSURLSession *theURLSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *theURLSessionDataTask = [theURLSession dataTaskWithURL:[NSURL URLWithString:theURLString]
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
{
NSLog(#"done");
}];
[theURLSessionDataTask resume];
As you can see, 30 MB are still lost.
UPDATE #2:
The above tests where done using Xcode simulator.
However, I have also decided to test on actual iOS 9.2 iPhone 4S (as recommended by #Sohil R. Memon).
The results of 'NSData dataWithContentsOfURL:' are below:
The results of using 'NSURLSession dataTaskWithURL:completionHandler:' are below:
It looks like 'NSData dataWithContentsOfURL:' works perfectly on actual device, while 'NSURLSession dataTaskWithURL:completionHandler:' -- doesn't.
However, does anyone know solutions which show identical information on BOTH actual device AND Xcode simulator?
Approximately 100 MB are gone for nothing in iOS 9.x. How can I get them back? Are there any workarounds?
For a couple of reasons, we should use NSURLSession to download data from a web service. So, this is not a workaround, but the correct approach.
What is going on in iOS 9.x?
I have no idea - possibly cached data, network buffers, or some other issues. But this is irrelevant - you should try the correct approach with NSURLSession.
From the docs:
IMPORTANT
Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTaskWithURL:completionHandler: method of the NSURLSession class. See URL Session Programming Guide for details.
Edit:
Those "reasons" are:
NSURLSession is specifically designed to load remote resources.
NSURLSession methods are asynchronous which is crucial for methods which complete only after a perceivable duration (it will not block the calling thread).
A session can handle authentication by means of a default method or with a custom delegate.
Session tasks can be cancelled.
Here is also an answer which helped me. The answer states to use [NSData dataWithContentsOfURL:url options:0 error:&error]; instead.
Hope this helps
I have a situation where I need to download multiple files sequentially as each download depends on its previous downloaded file. (I am processing the file in background itself)
I am using NSURLSessionConfiguration backgroundSessionConfiguration.
There is a scenario where NSURLSessionDownloadTask initiates while the application is in background. This crashes the app with Assertion permittedbackgroundduration.
So, my question is, am I doing wrong by initiating the download task in background???
Thanks in advance,
- Satya
You have limited time when on background, so chances are that you're taking too long to process the file.
If you don't call the completion handler fast enough, the system will crash your app.
Or you may have made a mistake and you are not calling the completion handler at all on the scenario where NSURLSessionDownloadTask initiates in background.
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.