handleEventsForBackgroundURLSession isn't fired for particular bundleID

his has been driving me potty for two days now, and I've eventually got to the bottom of it - but no idea where to go now.

App structure

  • Single view app, nothing in the view controller. App delegate has only boilerplate code, aside from implementing
    handleEventsForBackgroundURLSession
  • Framework that extends
    URLSessionDownloadDelegate
    ,
    URLSessionDelegate
    and
    URLSessionTaskDelegate
    . The methods implemented essentially just log messages.
  • Notification Service Extension. This handles push notifications which we want to do things in response to.
  • Targeting iOS 11, using a real device. I can't test on the simulator as we're using push notifications

What should happen

  • Push message received by the notification service extension. This creates a background
    URLSession
    with a
    sharedContainerIdentifier
    ,
    sessionSendsLaunchEvents = true
    and starts off a download.
  • The download completes, and, depending on the length, either returns in the
    didFinishDownloadingTo
    method of the Framework, or
    handleEventsForBackgroundURLSession
    on the app delegate. This behaviour is well documented here : https://forums.developer.apple.com/message/225659#225659


Background

I already have an app that has been using push notifications for a while, and want to implement this into. I created a test app with the above structure to prove it works, and it does. I then spent two days removing my hair while I was completely unable to get

handleEventsForBackgroundURLSession
to fire for longer running downloads.

I therefore have two projects, with essentially the same structure and code. It is now to the point where I've basically copied the working one into the non-working one. However, one works, and one does not.


Where it goes wrong

In a fit of rage and after exhausting every other sensible option, I decided to simply rename the bundle identifier on the working one, and refactor everything into that. And all of a sudden

handleEventsForBackgroundURLSession
is no-longer called.


The bundle is the bad guy

Yes - literally switching the bundle identifier between the two apps makes one work, and one not.


The logging

So in the console, we can see what is happening. When it's working, the

nsurlsessiond
process that is responsible for background downloads sends a request to open the app when it completes in order to fire the delegate. This simply doesn't happen in the non working instance.


The problem

The bundle identifier is the thing that ties everything together in the app store. Unless I want a whole new app (I don't) I'm stuck with the "broken" bundle ID and

handleEventsForBackgroundURLSession
never being called, which *****.

Has anyone ever seen anything like this in the past, and have ideas as to the cause and possible fix?

Is this problem new on iOS 13?

Do you have Settings > YourAppName > Background App Refresh enabled?

Share and Enjoy

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

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

I don't think it's iOS 13, but you might have hit it on the head with Background App Refresh! I didn't realise the settings would persist even after a reinstall so hadn't thought to look there. Make sense as to why changing the bundleID works.


Why would the affect the re-launch though? I thought that was really for scheduled stuff and it was

performFetchWithCompletionHandler
that got called in response to that? I'm pretty sure at one stage I didn't have any background modes enabled in the manifest and it was working - but I could be wrong there.


More investigation needed, but thank you!

Ok, so there's some interesting behaviour around all this.


I did have the "Background Modes" capabiliity enabled and "Remote notifications" and "Background processing" both ticked, as I thought I needed them. With those enabled, but background app refresh disabled (global or app-level), my delegates are never called.


So I have now removed all background modes from the capabilities, and I can still merrily create background URLSessions and have them run. So at an app level, I no longer have the background app refresh toggle.


With global background app refresh enabled, it seems one of the following occurs when the downloads complete:


* If the transfer completes before the 30 second time limit on the notification service extension, the didFinishDownloadingTo delegate gets called on the extension. No problems here.


* If the transfer takes longer than 30 seconds AND if my application is terminated (killed in multitasking) it gets relaunched and in my launch options, I've got the identifer for the session so I can recreate it and the delegates called.


* If the transfer takes longer than 30 seconds AND if the app is just backgrounded, it's handleEventsForBackgrounURLSession that gets called as the entrypoint.


Either way, I get told - yay!


With global background app refresh disabled, I get the following results:


* If the transfer completes before the 30 second time limit on the notification service extension, the didFinishDownloadingTo delegate gets called on the extension. Same as if background app refresh is enabled. All good (also tested on low power mode - same behaviour)


* If the transfer takes longer than 30 seconds (app in the background or hard killed), no delegates get called - which seems wrong?


One thing I have noticed, is I'm seeing a lot of -996 and -997 errors when creating the session in the service extension. The 997's seem to relate to *previous* downloads that ran but never had their delegates called (which I guess makes sense?) The -996 is a bit more of an issue, but I can handle that.


So we experience different behaviour depending on background app refresh being enabled or disabled - but my problem now is I can't find a way to determine the state of background app refresh from my extension, as it's on UIApplication.shared - which isn't available to me.


Is there any way to do this? It's a bit annoying the behaviour varies on a setting I can't check! If I know my delegate will get called eventually, I don't need to prompt my user to open the app as it'll get there in the end, but if it won't get called, I'd like to let them know it's on them.

With global background app refresh disabled …

iOS 13 introduces new behaviour (r. 47718087) whereby, if the user has turned Background App Refresh off in Settings — either globally or for your app, assuming it has this setting [1] — your app won’t be resumed (or relaunched) when the tasks complete in an

NSURLSession
background session. That is, your session will behave as if
sessionSendsLaunchEvents
were set to false.

If this new behaviour is causing your app significant amounts of grief, I recommend that you file a bug and explain your circumstances there. Please post your bug number, just for the record.

Share and Enjoy

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

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

[1] I’ve seen some cases where this logic is being applied even if the app doesn’t have the Background App Refresh setting (and thus there’s no way for the user to turn Background App Refresh back on again) and we’re tracking that as a bug (r. 55305812). Fortunately, it doesn’t seem to be affecting you.

Ok thanks - will have a think of how to word it 🙂

handleEventsForBackgroundURLSession isn't fired for particular bundleID
 
 
Q