I'm having difficulty in the download using NewsstandKit when the app shut down.
My newsstand app download does starts in Background Mode, and exit to the background or push a download notification, the download issue everything is OK, but i shut down the app then the task is downloading,the downloading task failed.
Does it has to do some thing with the Server? or others?
You need to resume your downloads when the App Launches again.
Something to the tune of this would work
// Inside App Delegate Did Finish Launching
NKLibrary *nkLib = [NKLibrary sharedLibrary];
for(NKAssetDownload *asset in [nkLib downloadingAssets]){
[asset downloadWithDelegate:newsstandDownloadDelegate];
}
If you read the documentation you will find the following paragraph detailing how you should handle app termination:
While assets are being downloaded, the application could be suspended
or even terminated entirely if, for instance, there is insufficient
memory or the user force-quits the application. The application
(assuming it has the newsstand-content property) is later relaunched
into the background to handle the completion of the download or any
authentication challenges or errors related to it. The procedure in
this case is the following:
When the application is launched, it can use the
UIApplicationLaunchOptionsNewsstandDownloadsKey key to access an array
in the launchOptions dictionary passed into the
application:didFinishLaunchingWithOptions:. If there is an array, it
contains the identifiers that caused the launch; these could be
identifiers for downloads that have finished downloading that or could
not complete downloading. It iterates through the array of
NKAssetDownload objects from the downloadingAssets property of the
NKLibrary object and calls downloadWithDelegate: on each of them. If
it wants, the application can use the asset identifiers obtained in
the previous step to check which asset downloads caused the relaunch.
The NSURLConnectionDownloadDelegate object handles the asset downloads
as it does normally. The Newsstand Kit framework expects all calls of
its methods to be made on the main thread; the NSURLConnectionDelegate
Protocol methods are also invoked on the main thread.
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'm trying to reach the file which I was downloading earlier by NSURLSession. It seems I can't read the location of the file, even though I'm doing it before delegate method ends (as the file is temporary).
Still, I'm getting nil when trying to access the data under location returned from NSURLSession delegate and error 257.
The code goes as following:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSError *movingError = nil;
NSData *fileData = [NSData dataWithContentsOfFile:location.path options:0 error:&movingError]; // is nil
NSLog(#"%#", movingError); // is error 257
}
What's wrong with this code..? I saw similar questions NSURLSessionDownloadTask - downloads but ends with error and iPhone - copying a file from resources to Documents gives an error but these completely doesn't apply to my case.
-- edit --
I've created a new project and pasted the very same code. It works... So:
1) In my project I'm receiving error 257, probably some configuration of the project is invalid or the fact I'm using backgroundTasks somewhere else in the app messes things up
2) Same as in 1 happens if I put the source files of this download to the external framework linked in by Carthage
3) On demo project I created (copy-pasted files used in 1 & 2) everything works corretly.
If someone has an idea what can cause the fact it isn't working - would be awesome.
Late to the discussion, but I've seen the same/similar issue and investigated.
With my investigation, what you are seeing is expected (should I say “it's observed”?).
But since I'm not 100% sure how you are using URL session, I put down below what I'm trying to do, followed by my observations (important ones only).
Also, I created a sample project and put it here as downLoader.zip. You can play and check how URL session background download works. But It's better to read thru below before trying to play, though this is fairly a long note.
A. what I'm trying to do
I'm developing kind a map app, where I need to download 1000+ of small size (0.5-150kB, mostly ~20kB) PNG files at a time. Total download size of them is ~50MB, and it takes a couple of minutes to download all of them. I think that keeping users to my app just waiting for download is bad design, so I made the app to use URL Session’s background download.
I have to admit, however, that Apple’s doc says that “Background sessions are optimized for transferring a small number of large resources that can be resumed as necessary.” So, the way I’m using background session is totally opposite. But, anyway...
B. what I've observed
Below, I’ve listed my observations and my guess of why. I should say guess since they are not documented.
(1) observation: sometimes, files that should come with didFinishDownlaodingTo don’t exist.
Temporary files are put at:
/var/mobile/Containers/Data/Application/randomHexString/Library/Caches/com.apple.nsurlsessiond/Downloads/yourName.yourApp. The randomHexString changes from app’s run to run. When the files don’t exist, the randomHexString that came with didFinishDownloadingTo is the one from the “previous” session. Now, “previous” here means the session at app’s previous run !, which clearly doesn't exist at the current session at current run.
There's another scenario for non-existent file, which is that the randomHexString is ok, but the files don't exist. This seems to happen after session cancel is requested (invalidateAndCancel) and before session becomes invalid (didBecomeInvalidWithError).
Guess: This happens especially on the development phase since we terminate the app while downloading, manually or by debugger or just by bug. It seems once the download requests are handed and accepted to the OS, the OS handles our requests even after the app quits. Note we can’t know if the OS has definitely accepted the requests or not. Even after the return from URLSessionDownloadTask:resume(), sometimes files don’t exit at the next launch and sometimes they do.
Workaround: If files don’t exist, just ignore them. In a while, “this time’s” files should come.
(2) observation: sometimes, the duplicated (same) files come with didFinishDownloadingTo.
My app converts downloaded PNG files to other format. W/in didFinishDownlaodingTo, I move temp files (== OS designated) to another my app’s directory, then spawn thread (GCD) to convert the format and delete downloaded temp files. So, the other thread (didFinishDownlaodingTo) to overwrite is an issue.
Workaround: I make list of URLSessionTask:taskIdentifier and w/in didFinishDownlaodingTo, check the duplication by retrieving the taskID list to ignore duplicated files.
(3) observation: even after the user terminated the app, OS relaunches the app again.
After the user terminated the app from task switcher, quite often, the OS relaunches the app w/ application:handleEventsForBackgroundURLSession:completionHandler.
Note the sequence of relaunch is that didFinishLaunchingWithOptions comes first as regular launch, then handleEventsForBackgroundURLSession comes next.
From user’s POV, when he/she terminated the app, that’s it, done! It looks strange even after they terminated the app, it relaunches in itself and notifies something to them. It's like a zombie.
Guess: The Apple’s document says “If the user terminates your app, the system cancels any pending tasks”. The definition of “pending tasks” is not clearly stated but I understand this is from iOS POV and not user’s or program developer’s. As guessed in (1), iOS seemed to have accepted the download requests, and they are not “pending tasks” anymore.
Workaround: I create a “flagFile” after all of download requests are handed to iOS w/ URLSessionDownloadTask:resume(), of which file means that “download is ongoing”. When users terminate the app, at UIApplicationDelegate:applicationWillTerminate, I delete the flag file. Or, if all of download requests not handed to iOS, there's no flag file. Then at app’s relaunch at UIApplicationDelegate:handleEventsForBackgroundURLSession, I check if we have the flag file. If missing, then I can assume that users terminated. Two choices here. Choice-1: I will not recreate URL session. What happens next is that iOS will terminate my app in about 20 seconds. I have no idea if this (== not creating the URL session) is a legal operation but it works. Users can launch w/in this 20 sec, so I put some more code to handle that scenario. Choice-2: I create URL session. What happens next is that iOS calls delegate methods didFinishDownlaodingTo/didCompleteWithError, followed by urlSessionDidFinishEvents. If I don't do anything here, the process (app) keeps alive indefinitely w/o any notification to users: nothing in task switcher. This is nothing more than waste of memory. The option here is to fire local notification and let users know of my app, so that they can go back to my app and can terminate (Again!), though my app clearly appears as a zombie. One issue for both choices: applicationWillTerminate may not be called in certain situation (though I've yet confirmed). At this case, flag file is left as regular ops and show zombie to users. So, the flag file method is just mitigation to the issue, but I think it works most of the time for my app.
Note the app is relaunched sometimes when it's killed by xcode debugger or killed by OS w/ bug (SEGFALUT).
(4) observation: after the app is terminated (by user, etc.) then relaunched by OS, the app is occasionally in active state (UIApplication.shared.applicationState is .active).
I want to notify the user on the download completion by local notification, but since it's active, local notification doesn't fire. So, I need to use UIAlertController instead. Therefore, I can't provide consistent user experience, and should look strange for users: most of the time local notification and very occasionally UIAlert. Note when app started in active state, it appears in task switcher.
Guess: totally no idea how this can happen. One good(?) thing is this happens only occasionally.
Workaround: seems none.
(5) observation: handleEventsForBackgroundURLSession/urlSessionDidFinishEvents is called just once.
I start the download after started background task (application.beginBackgroundTask). Then in expiration handler of beginBackgroundTask, I call endBackgroundTask. I don't know exactly why, but after endBackgroundTask, my process is still given lots of process time, so I can keep requesting download. This might be because download files keep coming w/ didFinishDownlaodingTo. To be a good citizen, I suspend to request further download, and fire local notification to user to put the app to foreground. Now, once I suspend the request, in 4-5 seconds, OS determines the URL session is over and handleEventsForBackgroundURLSession then urlSessionDidFinishEvents are called. This is one-off event. When the user put the app in foreground to resume download, then put it again in background, no handleEventsForBackgroundURLSession/urlSessionDidFinishEvents will come anymore. What's unclear to me is the definition of session's start and end. It seems the session starts at first URLSessionTask.resume(), then ends w/ timeout, which seems to be determined by URLSessionConfiguration.timeoutIntervalForRequest. However, setting large number here (1000 sec) doesn't affect anything, it's always 4-5 seconds.
Guess: no idea
Workaround: don't relay on urlSessionDidFinishEvents while the app is alive. Relay only when app relaunched by OS and at initial handleEventsForBackgroundURLSession/urlSessionDidFinishEvents.
===============
Below, I listed about the sample project (downLoader.zip). You can verify all of above with the sample.
The app has a list of download files. The number of files is 1921, and 56MB in total. They are 256x256 PNG map tile files that are located in a server managed by Geo Spatial Information Authority of Japan (GSI). After downloaded, they are moved to Library/Cache/download. If your device is jail-broken, you can view them w/ Filza.
crash itself to test relaunch
emulate background task expiration
logging to file since debugger doesn't work after relaunch by OS. The file is in Documents, can be moved to PC.
Do play with the real device. Simulator doesn't relaunch the app.
Project built w/ Xcode 8.3.3 and tested w/ iphone6+/9.3.3 and iphone7+/10.3.1
To see if app relaunched w/ xcode, go to Debug/Attach to Process menu and see if downLoader is listed.
===============
I think URL session background download behaves trickily, especially at relaunch. We need to consider at least the observations I listed above, or the app users will get confused.
I found same error code 257 from online users feedback.In the error scene, the location always refers to a strange bundle identifier:com.sdyd.SDMobileMixSmart. And I guess this's an apple system bug.
I have some misunderstanding in using NSURLSession framework, that's why I decided to write small app from scratch without AFFramework/Alamofire.
I have an API that requires following steps to upload file:
POST file data
Get response (JSON)
Post some json fields to api/save
I have a background session with such config:
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("myBackground")
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
I've implemented 2 methods:
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData)
where I aggregate all data
and
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?)
where I tranform this data to response object. This response object if VERY important for me.
Everything works fine, while app is in foreground, but I have problems in background.
Case 1
App crashed right after I've started to upload data. According to WWDC I need to implement
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void)
and call this handler in didCompleteWithError method. But before calling this method I need to call api/save with data from upload response.
How can I get this data?
Case 2
Mostly similar case. User stops app while upload is in progress. Than loads app in few seconds, while session works with my task. Now session calls didReceiveData, but of course, some of data is missing. What should I do in such case? How to restore response data?
You don't mention implementing URLSessionDidFinishEventsForBackgroundURLSession (a NSURLSessionDelegate method). You really want to implement that, too. The basic process is:
In app delegate's handleEventsForBackgroundURLSession, you should:
start the background NSURLSession (which will start receiving delegate method calls associated with all of the uploads); and
save the completion handler (but do not call it, yet).
Then, in URLSessionDidFinishEventsForBackgroundURLSession (when you're done processing all of the responses), you call the completion handler you saved in handleEventsForBackgroundURLSession. (Make sure to dispatch that to the main queue.)
If you're doing all of that, when the background session is restarted, the didReceiveData calls will come in with the responses to your various uploads.
I just did a quick test, uploading five 20mb images and immediately terminating the app. Then, even though the app wasn't running, I watched the five files slowly show up on my server (obviously handled by the daemon process). When all five were done, by app was transparently restarted in the background, the handleEventsForBackgroundURLSession was called (which restarted the session), it let all of the didReceiveData calls quickly get called, and when that was done, URLSessionDidFinishEventsForBackgroundURLSession was called and my app only then called the saved completion handler.
In terms of why this isn't working for you, there's not enough to diagnose the problem. Possibilities include:
Maybe you terminated the app inappropriately. You can't kill the app by double tapping the home button and terminating the app there; you have to let it naturally terminate on it's own, or for diagnostic/testing purposes, I force it to terminate by calling exit(0) in code.
Maybe you didn't restart the session when handleEventsForBackgroundURLSession was called.
Maybe you called the supplied completion handler too soon (i.e. before URLSessionDidFinishEventsForBackgroundURLSession was called).
It's hard to say, but I suspect that there's something buried inside your implementation that isn't quite right and it's hard to say what it is on the basis of the information provided (assuming it isn't one of the above points). Unfortunately, debugging this background sessions is vexingly complicated because when the app terminates, it is no longer attached to the debugger, so you can't easily debug what happens after the app is restarted automatically by iOS). Personally, I either NSLog messages and just watch the device console (as well as watching what appears on the server), or I build some persistent logging mechanism into the app itself.
For testing Background session code it is recommended to test on a real device. When writing an app that uses NSURLSession’s background session support, it’s easy to get confused by three non-obvious artifacts of the development process:
When you run your app from Xcode, Xcode installs the app in a new container, meaning that the path to your app changes. This can confuse NSURLSession’s background session support.
Note: This problem was fixed in iOS 9; if you encounter a problem with NSURLSession not handling a container path change in iOS 9 or later, please file a bug.
Xcode’s debugging prevents the system from suspending your app. So, if you run your app from Xcode, or you attach to the process some time after launch, and then move your app into the background, your app will continue executing in situations where the system would otherwise have suspended it.
Similarly, the iOS Simulator does not accurately simulate app suspend and resume; this has worked in the past but it does not work in the iOS 8 or iOS 9 simulators.
Source: Apple Developer Forum
I want to upload a file to a server using NSURLSession.
Cases Are:
1. It should resume uploading a file to the server from where it stopped because of app crash.
2. It should handle background upload as well.
Try AFNetworking Library to upload image asynchronously.You can find a brief example in this thread.
You should use background NSURLSession. If your app crashed or user left the app while upload was in progress, with a background NSURLSession the upload would continue seamlessly in the background. When the upload is done, your app will be notified of this via the delegate (and if your app wasn't alive at the time the download finished, it will be started in a background mode, at which point you can do whatever cleanup you need).
So create NSURLSessionConfiguration with backgroundSessionConfigurationWithIdentifier, and then instantiate a NSURLSession with that configuration.
There are a few caveats:
You cannot use completion handler pattern. You have to use delegate-based implementation.
You have to implement handleEventsForBackgroundURLSession in the app delegate, capturing the completionHandler it passes you and also instantiate the background session again. Likewise, in your NSURLSession delegate methods, you have to implement URLSessionDidFinishEventsForBackgroundURLSession, which will call the saved completion handler.
For more information, see Background Task Considerations in URL Session Programming Guide, see a section of the same name (but different text) in the NSURLSession class reference, or see the WWDC 2013 What's New in Foundation Networking, where Apple first introduced us to background sessions.
I have implemented NSURLSession for downloading fairly large files from our servers. Now as long as I'm working in foreground or background and go back to the app the transactions are working and getting finished.
But if I force-quit the app using the the multitasking screen and re-open the app again. the downloading process is not getting finished although as I understood from the docs, it should, here what the docs state:
If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. This behavior applies only for normal termination of the app by the system. If the user terminates the app from the multitasking screen, the system cancels all of the session’s background transfers. In addition, the system does not automatically relaunch apps that were force quit by the user. The user must explicitly relaunch the app before transfers can begin again.
Meaning if I relaunch the app the transaction before the force-quit should started again, or are they? Is there an additional operation I need to commit in order this to work?
UPDATE: I stumbled on this project:
https://github.com/Heikowi/HWIFileDownload#force-quit
That states:
Force Quit
After the app has been killed by the user, downloads do not continue in the background. On iOS 7 (and later) resume data is passed back.
Meaning there is a way to receive resume data even if the application was killed by the user in the background. Only the project is written in Objective-C and I can't understand what are they doing to achieve this.
After a force-quit the:
NSURLSessionTaskDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
delegate method will be called when the app is restarted. If the download task can be resumed the error object will contain the resume data:
[error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData].
Using this data you can resume the download process by creating a NSURLSessionDownloadTask with:
(NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData*)resumeData.
More info about that can be found in Life Cycle of a URL Session with Custom Delegates, step 13.
I think that after your application did force-quit you should start all over again (.
If the user terminates your app, the system cancels any pending tasks.
and
When all of the tasks associated with a background session are complete, the system relaunches a terminated app (assuming that the sessionSendsLaunchEvents property was set to YES and that the user did not force quit the app)
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
-> use URLSession background Sessions download doesn't stop at all....you don't have to explicitly code for resuming the download or something..
https://developer.apple.com/reference/foundation/urlsession
check for background session in this link...if you're not able get the still...comment me and i would help in detail.