NSURLSessionDownloadDelegate Methods on iPhone iOS 10.3.1

I've recently been integrating Background Transfer Service into an application so that the user is able to download files in the background.


I did most of my building/testing on an old iPhone 6 device that was running iOS 9.3.5 (I try to keep my previous device back a version of iOS). Background Transfer Service works great on this device. I am able to put 100+ download tasks into the queue and they all finish and report progress as expected after sending the application into the background and then re-opening the application.


This also appears to work great on a new 5th generation iPad running iOS 10.3.


Where things get weird is on my iPhone 7 Plus which is running iOS 10.3.1. The downloads kick off fine, progress is reported as expected in the didWriteData method, however if I background the application and wait ~10 seconds and re-open the application the progress never catches back up to what progress would have been done in the background and the progress never increments at all (it stays where it was when it was backgrounded). The didFinishDownloading method does end up getting called however it appears to wait until the very end of all the download tasks completions for that method to fire for all of the remaining tasks. So the didWriteData, didFinishDownloading, didCompleteWithError, etc. all happen right at the end in one burst.


I have however had a few instances where my iPhone 7 Plus device running iOS 10.3.1 did show progress after bringing the app back into the foreground however those instances I could could on one hand. More often than not (9 times out of 10) the progress is never reported on this device after re-opening the application.


I am at a bit of a loss and am wondering if there is a known bug we should be looking out for, and if so when we could expect a fix? Or if there are currently any known work arounds. From my testing/debugging I cannot get progress to work in my application on the iPhone 7 Plus running 10.3.1. I also had a co-worker test this as well and they also experienced this.


In fact they went a step further and opened the Netflix application on their 10.3.1 iPhone 7 Plus device and began downloading a movie. Progress incremented as expected, they then backgrounded the application and waited ~20 seconds and re-opened the application, only to find the progress get stuck where it was when the app was initially backgrounded and it never moves. After a given amount of time of the progress not moving the download does eventually finish (the progress indicator disappears) and the Netflix movie is downloaded and able to be played. This is the same behavior I see above in my application.


Any help with this would be greatly appreciated.


Thanks in advance!


Adam

Replies

I have found a workaround for the issue. Once the application did return from background mode, make sure to call resume on all running tasks. This seems to reactivate callbacks to the delegate.


private(set) var session: URLSession?

func applicationDidBecomeActive(_ application: UIApplication) {
     session?.getAllTasks(completionHandler: { tasks in
          for task in tasks {
               task.resume()
          }
     })
}

For me at least, this worked reliably and now I can see progress again.

Would be nice to fix the issue or put this information into the documentation.

I have found a workaround for the issue.

Your workaround worried me a little because you’re calling

resume
redundantly, and I wasn’t sure what impact that might have on the session. So I discussed this issue with the
NSURLSession
engineering team and they suggested that the call to
resume
was unnecessary, and just calling
getAllTasks(completionHandler:)
should be sufficient to get delegate events flowing again.

I did some testing of this here in my office and it seems to work, but it’s hard for me to be 100% sure because my standard background session test code was already calling

getAllTasks(completionHandler:)
as part of its normal operations.

So, my question is: If you remove the call to

resume
, is your workaround still effective?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hello Eskimo


I'm experiencing exactly the same problem as everyone in this thread.


First I can confirm the @gebirgsbaerbel workaround DO work.

Then I tried to only call `getAllTasks(completionHandler:)`, as you suggested, but it does not work.


I also noticed a strange thing and going to explain.


There are 3 uses case :

1/ App is foreground during the whole download

2/ App starts the download foreground, then goes background, then completes it

3/ App starts the download foreground, then goes background, then goes foreground again, then completes it


During 1/ here's the callbacks succession:

1• urlSession(assetDownloadTask:didFinishDownloadingTo)

2• urlSession(task:didCompleteWithError)


During 2/ here's the callbacks

1• application(handleEventsForBackgroundURLSession:completionHandler:)

2• urlSession(assetDownloadTask:didFinishDownloadingTo)

3• urlSession(task:didCompleteWithError)

4• urlSessionDidFinishEvents(forBackgroundURLSession)


During 3/

without using the workaround, the same callbacks as 2/ ⚠

using the workaround, the same callbacks succession as 1/


=> So during 3/ the background callbacks are called, as if the application was in background.

as if the session doesn't realize the app did enter foreground before the end of the download.


EDIT: I've done all my tests on a blank new & simple app which you can found here: http://appricot.fr/AssetDLTest.zip

If you want to reproduce the probem, you should comment the workaround, from lines 71 to 76 of ViewController.swift

I also noticed a strange thing and going to explain.

Thanks. Your code and detailed instructions highlighted a number of flaws in my understanding of this problem. I’ve since corrected those and now see the behaviour you describe, that is,

-getAllTasksWithCompletionHandler:
is insufficient but
-getAllTasksWithCompletionHandler:
and
-resume
works.

Another interesting thing to note is that this workaround has to be done out of

-applicationDidBecomeActive:
. I originally tried doing it from
-applicationDidEnterBackground:
and that didn’t work.

And now I’m off to determine if

NSURLSession
engineering is prepared to officially endorse this workaround…

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

This is a really helpful thread, I've been pulling my hair for days!


Any updates on Apple's front, do we have to do the workaround no matter what ?

Indeed this thread is describing the exact isuse I'm seeing, Any updates on this?

Any updates on this?

No, although I haven’t yet given up.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

And now I’m off to determine if

NSURLSession
engineering is prepared to officially endorse this workaround…

My persistence paid off! (-:

NSURLSession
engineering took a look at this problem and the suggested workaround and they’re OK with folks using it. Their analysis is that you don’t need to call
-resume
on every task — calling it on just one task should do the trick — but doing so shouldn’t cause problems.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the update, it helped me a lot. As the dozens of posts you've made on that forum 🙂

Do you think there's a way to change the title of this post though ? I couldn't find it until I opened a TSI that redirected me here, it does not seem very straightforward and easy to find...

Thank you for looking into this. This "workaround" still seems necessary as it helped me resolve this issue today in 2020 on iOS 13.3.1.

This still appears to be an issue in iOS 14.4 and the workaround still works. I did find out that if your app uses scenes you'll need to place the workaround code in your SceneDelegate in sceneDidBecomeActive(_:) instead of applicationDidBecomeActive as the latter is never called. Thanks to everyone who pitched in on this issue over the years!

Any news @eskimo ? I still don't dare to touch the workaround code :)

Any news

The bug mentioned upthread (r. 32247561) is still open. It’s possible that the issue was resolved ‘accidentally’ in the 6 years since I last looked at it. Are you still able to reproduce the problem?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"