1

I have finally (ignoring the sample code which I never saw work past "application task received, start URL session") managed to get my WatchOS3 code to start a background URL Session task as follows:

 func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {

    for task in backgroundTasks {
        if let refreshTask = task as? WKApplicationRefreshBackgroundTask {
            // this task is completed below, our app will then suspend while the download session runs
            print("application task received, start URL session")

            let request = self.getRequestForRefresh()
            let backgroundConfig = URLSessionConfiguration.background(withIdentifier: NSUUID().uuidString)
            backgroundConfig.sessionSendsLaunchEvents = true
            backgroundConfig.httpAdditionalHeaders = ["Accept":"application/json"]
            let urlSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)
            let downloadTask = urlSession.downloadTask(with: request)

            print("Dispatching data task at \(self.getTimestamp())")
            downloadTask.resume()

            self.scheduleNextBackgroundRefresh(refreshDate: self.getNextPreferredRefreshDate())
            refreshTask.setTaskCompleted()
        }
        else if let urlTask = task as? WKURLSessionRefreshBackgroundTask {
            //awakened because background url task has completed
            let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: urlTask.sessionIdentifier)
            self.backgroundUrlSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil) //set to nil in task:didCompleteWithError: delegate method

            print("Rejoining session ", self.backgroundUrlSession as Any)
            self.pendingBackgroundURLTask = urlTask //Saved for .setTaskComplete() in downloadTask:didFinishDownloadingTo location: (or if error non nil in task:didCompleteWithError:) 

        } else {
            //else different task, not handling but must Complete all tasks (snapshot tasks hit this logic)
            task.setTaskCompleted()
        }
    }
}

However, the issue I am now seeing is that my delegate method urlSession:task:didReceiveChallenge: is never being hit, so I cannot get my download to complete. (I have also added the session level urlSession:didReceiveChallenge: delegate method and it is also not being hit).

Instead I immediately hit my task:didCompleteWithError: delegate method which has the error:

"The certificate for this server is invalid. You might be connecting to a server that is pretending to be ... which could put your confidential information at risk."

Has anyone gotten the background watch update to work with the additional requirement of hitting the didReceiveChallenge method during the background URL session?

Any help or advice you can offer is appreciated.

CodenameDuchess
  • 1,221
  • 18
  • 24
  • BTW this code works fine for background refresh on a production box where I do not receive the invalid certificate error. (Xcode 8.1 with WatchOS3) – CodenameDuchess Nov 10 '16 at 06:00
  • This actually works for you? You receive a WKURLSessionRefreshBackgroundTask when the download is finished? – ian Feb 21 '17 at 08:22
  • Yes this works for us. Make sure you are placing yourself as the delegate on the session when you create it. Also note the WKURLSessionRefreshBackgroundTask block is necessary (even though you may not be able to hit it with a breakpoint). Be sure not to complete the task in that block but hold onto it and complete it after your didFinishDownloadingTo or didCompleteWithError delegate method's logic. – CodenameDuchess Feb 21 '17 at 21:26
  • I have background refresh working like in your example too, by setting the delegate on the URLSession. The didFinishDownloadingToURL delegate method gets called and everything seems to work. But that is not the way it's supposed to work, and I would prefer to do it the proper way, if only it worked... – ian Feb 22 '17 at 08:11
  • I was very frustrated with that fact as well. I am used to Apple's code examples working like a charm. But when I downloaded their background refresh code example and never saw it work on my watch I eventually had to give up and go with what works. I cobbled this implementation together based on both other frustrated dev's comments in Apple forums and trial and error. (Thank you Daniel Fontes wherever you are). All I can say is it works. How it's "supposed" to work I don't know. But I'm glad my example helped you out. :-) https://forums.developer.apple.com/message/156604#170909 – CodenameDuchess Feb 22 '17 at 20:17

1 Answers1

3

As it turns out the server certificate error was actually due to a rare scenario in our test environments. After the back end folks gave us a work around for that issue this code worked fine in both our production and test environments.

I never hit urlSession:task:didReceiveChallenge: but it turned out I did not need to.

Made a minor un-related change:
Without prints/breakpoints I was sometimes hitting task:didCompleteWithError Error: like a ms before I hit downloadTask:didFinishDownloadingTo location:.

So I instead set self.pendingBackgroundURLTask completed in downloadTask:didFinishDownloadingTo location:. I only set it completed in task:didCompleteWithError Error: if error != nil.

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {

    //Complete task only if error, if no error it will be completed when download completes (avoiding race condition)
    if error != nil {
        self.completePendingBackgroundTask()
    }

}

func completePendingBackgroundTask()
{
    //Release the session
    self.backgroundUrlSession = nil

    //Complete the task
    self.pendingBackgroundURLTask?.setTaskCompleted()
    self.pendingBackgroundURLTask = nil
}

Hope someone else finds this helpful.

CodenameDuchess
  • 1,221
  • 18
  • 24
  • 1
    So you don’t ever use `invalidateAndCancel()`? – francisaugusto Feb 03 '18 at 14:21
  • 1
    No I didn't but that would definitely be a good thing to add before setting the backgroundUrlSession to nil. – CodenameDuchess Feb 07 '18 at 20:35
  • 1
    Thank you! I was also racily completing the task too soon, which I now do in the `URLSessionDidFinishEventsForBackgroundURLSession` delegate method. It's documented to mean "all messages have been delivered", although only with reference to iOS. Also mentioned in that Fontes answer. This also seems to work in the simulator now. – Rhythmic Fistman Oct 19 '18 at 10:45
  • 1
    this works, but it is wierd why isnt WKURLSessionRefreshBackgroundTask called – João Nunes Feb 23 '19 at 18:17