NSURLSession with share extension returns -995 on OSX

Hi,


I'm trying to upload data using NSURLSession with a background task from an OSX share extension.


As soon as I start the task, by delegate is called back with the world's least helpful error message:


The operation couldn’t be completed. (NSURLErrorDomain error -995.)


There's no other information in the NSError object, nor in the console.


After scouring the internet, the only clue I have is to make sure that I've set up the `configuration.sharedContainerIdentifier` correctly, however I've already done that:


let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(uniqueId)

configuration.sharedContainerIdentifier = Config.appGroupName

urlSession = NSURLSession.init(configuration: configuration, delegate: self, delegateQueue: nil)


I then prepare the request and create the task:


let task = self.urlSession!.dataTaskWithRequest(request)

self.tasks.append(task)

task.resume()


Note that everything works perfectly when from my main app. It's just the sharing extension that fails.

What other problems could cause error -995?

Once you know that error -995 is

NSURLErrorBackgroundSessionRequiresSharedContainer
(see
<Foundation/NSURLError.h>
), it’s pretty clear why this is failing.

Even after you’ve got the shared container issue sorted, doing background networking from a share extension is quite tricky. You can find a long discussion of this on the old DevForums.

Share and Enjoy

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

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

Hi Quinn,


The thing is, I'm sure that


1) I have a shared container

2) I'm setting the shared container identifier on the session configuration


Before I do the upload I save the data to a temp file. I'm getting a temp location like this:


if let containerURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier(Config.appGroupName) {

return containerURL

}


The location is returned in my shared group folder, and I am able to write my temp file there, so I know the shared container is set up and working.


Is there anything else I can do to ensure that the shared container is ok? There must be some subtlety that I'm missing...

Does the session identifier have to have a particular name?


Tim

I'm getting the same error - NSURLErrorBackgroundSessionRequiresSharedContainer - in an OS X share extension that can otherwise access the group container.


I am able to use the same code in the containing application to successfully download in the background (using downloadTaskWithRequest). However, the downloaded file is not in the group container - it's in the containing application's sandbox container. The behavior in the containing application is the same regardless of whether sharedContainerIdentifier is set or not.


I've verified that calling containerURLForSecurityApplicationGroupIdentifier with the sharedContainerIdentifier returns the correct group container URL, which I can read from and write to in both the extension and containing application. I've tried creating the both the NSURLSessionConfiguration and the NSURLSession in Objective-C instead of Swift, with the same results.


Edit: I should also mention that I have an existing app with a Share Extension (in the App Store). I've just verified that app does download to the group container correctly when compiled with the latest Xcode (Version 7.3 7D175) - and is set up in an identical fashion to the app I'm now trying this in. The existing app is built with RubyMotion, so there are some underlying differences.

The old DevForums link displays "There was an error processing your request" - is there another way to view that discussion?

The old DevForums link displays “There was an error processing your request”

Indeed. It wasn’t when I posted the URL. I’ve reported it to the right folks.

is there another way to view that discussion?

I’ve pasted a copy in below.

Share and Enjoy

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

let myEmail = “eskimo” + “1” + “@apple.com”

I recently investigated this as part of a DTS incident and came back with a bunch of advice…

All of the following assumes iOS 8.1. I haven’t tested in iOS 8.0.

The specific focus here is a share extension, although the behaviour is likely to be similar for other short-lived extensions.

I wasn’t using SLComposeServiceViewController although I have no reason to believe that makes a difference.

I was testing on a device, not the simulator. In my experience the simulator is much less likely to terminate a share extension, which affects how things behave as I’ll explain later.

My app and its share extension have an app group in common. I started by verifying that it was working as expected (using NSUserDefaults).

The app and the share extension must use the same NSURLSession background session identifier.

When an NSURLSession background session is shared like this, it’s critical to understand that the session only allows one process to ‘connect’ to it a time. If a process is connected to the session and another tries to connect, the second process has its session immediately invalidated with

NSURLErrorBackgroundSessionInUseByAnotherProcess
.

The connected session is the one that receives the session’s delegate callbacks.

IMPORTANT If callbacks are generated when no process is connected, the background session resumes (or relaunches) the app (rather than the extension).

Also, if a process is connected to a session and is then suspended or terminates, the session disconnects internally. If the process was terminated, the reconnection happens when your code creates its NSURLSession object on next launch. If the process was suspended, the reconnect happens when the app is resumed with the

-application:handleEventsForBackgroundURLSession:completionHandler:
delegate callback.

The only way to programmatically disconnect from a session is to invalidate it.

The expected behaviour here is that the extension will start an NSURLSession task and then immediately quit (by calling

-completeRequestReturningItems:completionHandler:
). The system will then resume (or relaunch) the main app to handle any delegate callbacks.

When the system resumes or relaunches the main app to handle background session events, it calls

-application:handleEventsForBackgroundURLSession:completionHandler:
. The main app is expected to:
  1. save away the completion handler block

  2. reconnect to the session (if necessary) — This involves creating the NSURLSession object if it doesn’t currently exist.

  3. handle delegate events from that session

  4. invalidate the session when those events are all done — The app knows this because the session calls the

    -URLSessionDidFinishEventsForBackgroundURLSession:
    delegate callback.
  5. call the completion handler block that was saved in step 1

This leaves the app disconnected from the session, so future invocations of the extension don’t have to worry about the

NSURLErrorBackgroundSessionInUseByAnotherProcess
problem I mentioned earlier.

This design works best if each extension hosted by the app has its own shared session. If the app hosts multiple extensions, and they all used the same shared session, they could end up stomping on each other.

In my tests I’ve noticed that some annoying behaviour falls out of this design: if you start a task from an extension, it’s non-deterministic as to whether the app or extension gets the ‘didCompleteWithError’ callback. If the task runs super quickly, the extension typically gets the callback. If the task takes longer, the system has time to terminate the extension and the app is resumed to handle it.

There’s really no way around this. The workaround is to put the code that handles request completion in both your app and your extension (possibly reusing the code via a framework).

It would be nice if the extension could disconnect from the session immediately upon starting its request. Alas, that’s not currently possible (r. 1,8748,008). The only way to programmatically disconnect from the session is to invalidate it, and that either cancels all the running tasks (

-invalidateAndCancel
) or waits for them to complete (
-finishTasksAndInvalidate
), neither of which is appropriate.

One interesting edge case occurs when the app is in the foreground while the share extension comes up. For example, the app might have a share button, from which the user can invoke the share extension. If the share extension starts an NSURLSession task in that case, it can result in the app’s

-application:handleEventsForBackgroundURLSession:completionHandler:
callback being called while the app is in the foreground. The app doesn’t need to behave differently in this case, but it’s a little unusual.

21 Nov 2014

I'm getting the same error -

NSURLErrorBackgroundSessionRequiresSharedContainer
- in an OS X share extension that can otherwise access the group container.

I dug into this in depth recently (s. 639,346,255) and discovered two potential causes:

  • NSURLSession’s shared session support requires that the group ID (that is, the value in the

    sharedContainerIdentifier
    property) be listed in the
    com.apple.security.application-groups
    entitlement of both the app and the appex. You can confirm that by dumping the entitlements of these using
    codesign -d --entitlements :- /path/to/item
    .

    IMPORTANT Make sure you dump the entitlements of the appex inside your app, rather than the appex at the top of your build products directory, to avoid any possibility of things changing as Xcode copies the appex into the app.

  • Internally NSURLSession checks these entitlements. There seems to be an issue where the infrastructure that does this check needs read-only access to the app (and the appex nested inside). I found that when I ran my app from my build products directory (deep in

    ~/Library
    ), things failed with the
    NSURLErrorBackgroundSessionRequiresSharedContainer
    error. However, if I moved my app to some widely-accessible location (
    /Applications
    , say), things worked just fine.

    Clearly this behaviour is a bug. I don’t yet have a bug number for that. If you can reproduce the problem, please try the workaround I’ve suggested. If that fixes the problem then I’d appreciate you filing a bug report about this. And, in that case, 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"

Hi Quinn - thanks for the update.


I can confirm that moving the app to the /Applications folder allows the background session to work in the extension. However, the background session downloads in both the containing app and the extension still go to the app-specific sandbox folders.


I created a simple test project to reproduce the issue, but - after fixing an issue where the TeamIdentifierPrefix variable used in the containing app's App Group name was blank - this simple test app works in both regards (background sessions work, and downloads go to the group container) regardless of whether it has been moved to the /Applications directory.


To try and narrow down the problem, I created a new project with a different name, and progressively added each piece of my existing application to it. At this point, it is still working correctly - despite not being in the /Applications directory - and I've moved over all the code. The only piece I have not moved over is applying a specific provisioning profile. I'll post an update (and bug report) if I can narrow down what exactly causes it to stop working.

NSURLSession with share extension returns -995 on OSX
 
 
Q