2

I am developing an app and I need to update data in my app every day. I decided to use Background Fetch. I am downloading the data from an API so I am using URLSession. Since no completion handlers are allowed in performFetchWithCompletionHandler I am using delegate for that purpose. But my problem is that when I try to update the data when my app is not running the function didRecieve data is not called. Am I doing something wrong or should I use something else to update my data every day from an API?

My code is below:

func createTask(url: String, id: String){
    let accessKey = UserDataService().getCurrentUser().accessToken

    let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: id + UUID().uuidString)
    let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)


    var request = URLRequest(url: URL(string: url)!)
    request.setValue("Bearer \(accessKey!)", forHTTPHeaderField: "Authorization")
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "GET"

    let task = backgroundSession.dataTask(with: request)

    task.resume()

    print("task resumed")
}

This function gets called inside performFetchWithCompletionHandler and creates new dataTask but didRecieve data is not called.

I also tried adding this code inside performFetchWithCompletionHandler

print("BG FETCH")

let url = "secret url"

var request = URLRequest(url: URL(string: url)!)
request.setValue("SOME KEY", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"

URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
    print("DATA",data)
    completionHandler(.newData)
}).resume()

Thank you for any suggestion!

Phyber
  • 1,368
  • 11
  • 25
  • I'm not sure, but highly doubt it that you can *start* a backgroundSession from the backgroundState. You should only be able to continue it if it was *already* started in foreground. Imagine what would happen if apps were able to have long background downloads and the user never know and the realizes 5Gb was downloaded using his cellular data. They'd delete your app right away! – mfaani Aug 11 '17 at 17:31
  • @Honey What do you suggest then. My app depends on updates. – Phyber Aug 11 '17 at 17:33
  • I haven't used background App refresh that much, but looked into it and didn't see any mention of : "no completion handlers are allowed in performFetchWithCompletionHandler" <-- why are you saying this? Is this based on documentation? – mfaani Aug 11 '17 at 17:37
  • 1
    @Honey Confirmed by [this](https://stackoverflow.com/questions/41191747/ios-completion-handler-blocks-are-not-supported-in-background-sessions-use-a) and Duncan's answer – Phyber Aug 11 '17 at 17:38
  • @Honey I am using backgroundSession. Anyway do you suggest any other way how to update data in my app to be ready when user opens the app? – Phyber Aug 11 '17 at 17:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151743/discussion-between-honey-and-phyber). – mfaani Aug 11 '17 at 18:16
  • your confusing things. urlsession can have [different kinds of sessions](https://developer.apple.com/documentation/foundation/nsurlsession) (default, shared, ephemeral, background). If you you use the backroundSession then 1. your downloads would continue...if started from foreground (I don't know what happens if you start from backgroundFetch...I think it won't work) 2. You can't use completionHandlers. yet you can do a normal [sharedSession](https://developer.apple.com/documentation/foundation/nsurlsession/1409000-sharedsession?language=objc) or default... in a backgroundFetch – mfaani Aug 11 '17 at 18:37

1 Answers1

2

As I recall only uploading downloading are supported in the background, not data tasks.

You're right that you can't use the calls that take a completion handler. You have to provide a delegate.

Do a search on the string "Downloading Content in the Background" in Xcode. There's pretty extensive documentation.

The gist of it is that the system will re-launch your app if needed and call its application:handleEventsForBackgroundURLSession:completionHandler: method.

You have to set up the proper app permissions in your info.plist file. (I don't remember if you have to ask the user for permission to do background downloads or not. See the docs.)

Below is the first part of the info from Apple's Xcode documentation:

Downloading Content in the Background

When downloading files, apps should use an NSURLSession object to start the downloads so that the system can take control of the download process in case the app is suspended or terminated. When you configure an NSURLSession object for background transfers, the system manages those transfers in a separate process and reports status back to your app in the usual way. If your app is terminated while transfers are ongoing, the system continues the transfers in the background and launches your app (as appropriate) when the transfers finish or when one or more tasks need your app’s attention.

To support background transfers, you must configure your NSURLSession object appropriately. To configure the session, you must first create a NSURLSessionConfiguration object and set several properties to appropriate values. You then pass that configuration object to the appropriate initialization method of NSURLSession when creating your session.

The process for creating a configuration object that supports background downloads is as follows:

Create the configuration object using the backgroundSessionConfigurationWithIdentifier: method of NSURLSessionConfiguration. Set the value of the configuration object’s sessionSendsLaunchEvents property to YES. if your app starts transfers while it is in the foreground, it is recommend that you also set the discretionary property of the configuration object to YES. Configure any other properties of the configuration object as appropriate. Use the configuration object to create your NSURLSession object. Once configured, your NSURLSession object seamlessly hands off upload and download tasks to the system at appropriate times. If tasks finish while your app is still running (either in the foreground or the background), the session object notifies its delegate in the usual way. If tasks have not yet finished and the system terminates your app, the system automatically continues managing the tasks in the background. If the user terminates your app, the system cancels any pending tasks.

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) and calls the app delegate’s application:handleEventsForBackgroundURLSession:completionHandler: method. (The system may also relaunch the app to handle authentication challenges or other task-related events that require your app’s attention.) In your implementation of that delegate method, use the provided identifier to create a new NSURLSessionConfiguration and NSURLSession object with the same configuration as before. The system reconnects your new session object to the previous tasks and reports their status to the session object’s delegate.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • OK, I did know the exact things that you've written here. Only uploading downloading are supported in the background, not data tasks. You can't use the calls that take a completion handler. You have to provide a delegate. However, when I debug my code with background fetch, I did make a dataTaskWithRequest, and the most interesting part is that I did use calls taking a completion handler, and the debugger did stop at the breakpoint that I had placed in callback function. The project was written in Swift 2.3, can it be related with the Swift versions? – Burak Mar 20 '18 at 14:45
  • The term "in the background" is used a couple of different ways. It can mean while your app is still running, but another app is frontmost. Data tasks will continue to run then. If your app is suspended (still in memory but not getting CPU time) Then I believe a data task will also be suspended, and will continue when your app gets time again. The other sense of "background download", the one Apple is using, is where the system takes over management of the download and will keep it going even if your app is terminated. For that, you need to use a background task and delegates. – Duncan C Mar 20 '18 at 22:38
  • I'd be very grateful if you share with me how to simulate the one Apple is using where the OS takes over, even if the app is terminated, on simulator or on device. – Burak Mar 21 '18 at 07:34
  • I don't have any non-proprietary code lying around to show how to do this. The docs are pretty clear, however, and spell the steps needed. (I quoted the most important bits above.) – Duncan C Mar 22 '18 at 13:50