WKRefreshBackgroundTask doesn't working

1.8k views Asked by At

I'm trying to do my watch app running in the background mode. For starting doing this, I copied the example from this apple guide:

https://developer.apple.com/library/content/samplecode/WatchBackgroundRefresh/Listings/WatchBackgroundRrefresh_WatchKit_Extension_MainInterfaceController_swift.html#//apple_ref/doc/uid/TP40017295-WatchBackgroundRrefresh_WatchKit_Extension_MainInterfaceController_swift-DontLinkElementID_10

But it isn't working at all. This is my code:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController, WKExtensionDelegate, URLSessionDownloadDelegate {

@IBOutlet var unlink: WKInterfaceLabel!

@IBAction func startActivity() {
    // fire in 20 seconds
    let fireDate = Date(timeIntervalSinceNow: 20.0)
    // optional, any SecureCoding compliant data can be passed here
    let userInfo = ["reason" : "background update"] as NSDictionary

    WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: userInfo) { (error) in
        if (error == nil) {
            print("successfully scheduled background task, use the crown to send the app to the background and wait for handle:BackgroundTasks to fire.")
        }
    }
}

let sampleDownloadURL = URL(string: "http://devstreaming.apple.com/videos/wwdc/2015/802mpzd3nzovlygpbg/802/802_designing_for_apple_watch.pdf?dl=1")!

// MARK: WKInterfaceController

override func awake(withContext context: Any?) {
    super.awake(withContext: context)

    // Configure interface objects here.
    WKExtension.shared().delegate = self
    updateDateLabel()
}

let sampleDownloadURL = URL(string: "http://devstreaming.apple.com/videos/wwdc/2015/802mpzd3nzovlygpbg/802/802_designing_for_apple_watch.pdf?dl=1")!

// MARK: WKExtensionDelegate
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for task : WKRefreshBackgroundTask in backgroundTasks {
        print("received background task: ", task)
        // only handle these while running in the background
        if (WKExtension.shared().applicationState == .background) {
            if task is WKApplicationRefreshBackgroundTask {
                // this task is completed below, our app will then suspend while the download session runs
                print("application task received, start URL session")
                scheduleURLSession()
            }
        }
        else if let urlTask = task as? WKURLSessionRefreshBackgroundTask {
            let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: urlTask.sessionIdentifier)
            let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)

            print("Rejoining session ", backgroundSession)
        }
        // make sure to complete all tasks, even ones you don't handle
        task.setTaskCompleted()
    }
}

// MARK: Snapshot and UI updating

func scheduleSnapshot() {
    // fire now, we're ready
    let fireDate = Date()
    WKExtension.shared().scheduleSnapshotRefresh(withPreferredDate: fireDate, userInfo: nil) { error in
        if (error == nil) {
            print("successfully scheduled snapshot.  All background work completed.")
        }
    }
}

func updateDateLabel() {
    let currentDate = Date()
    self.unlink.setHidden(false)
    unlink.setText(dateFormatter.string(from: currentDate))
}

// MARK: URLSession handling

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    print("NSURLSession finished to url: ", location)
    updateDateLabel()
    scheduleSnapshot()
}

func scheduleURLSession() {
    let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: NSUUID().uuidString)
    backgroundConfigObject.sessionSendsLaunchEvents = true
    let backgroundSession = URLSession(configuration: backgroundConfigObject)

    let downloadTask = backgroundSession.downloadTask(with: sampleDownloadURL)
    downloadTask.resume()
}

}

It prints successfully scheduled background task, use the crown to send the app to the background and wait for handle:BackgroundTasks to fire, then I send the app to background but never enter on handle method.

Any help?! Thank you!!!

2

There are 2 answers

0
milczi On

You have set preferredFireDate to 10 seconds. As apple said there is no guaranty to fire handle method so quickly.

preferredFireDate

The time of the next background snapshot refresh task. The system makes every effort to wake your app in the background at some point after the scheduled time, but the precise time is not guaranteed.

When I set this parameter to 30 seconds I had to wait about 10 minutes till system called handle method.

Edit

Background app refresh tasks are budgeted. In general, the system performs approximately one task per hour for each app in the dock (including the most recently used app). This budget is shared among all apps on the dock. The system performs multiple tasks an hour for each app with a complication on the active watch face. This budget is shared among all complications on the watch face. After you exhaust the budget, the system delays your requests until more time becomes available.

2
CodenameDuchess On

I never got Apple's example to work either. But I did get background refresh working with the following code. Note that unlike Apple's example you will need to make yourself the delegate on the background session. (Also you don’t really need the “self.” in Swift, I just use it here to indicate the properties/functions).

var backgroundUrlSession:URLSession?
var pendingBackgroundURLTask:WKURLSessionRefreshBackgroundTask?

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")

            if let request = self.getRequestForRefresh() {
                let backgroundConfig = URLSessionConfiguration.background(withIdentifier: NSUUID().uuidString)
                backgroundConfig.sessionSendsLaunchEvents = true
                backgroundConfig.httpAdditionalHeaders = ["Accept":"application/json"]
                //Be sure to set self as delegate on this urlSession
                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)
            //Be sure to set self as delegate on this urlSession
            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()
        }
    }
}

func getRequestForRefresh() -> URLRequest? {

    guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon") else{
        return nil
    }

    return URLRequest(url: url)
}

// MARK: URLSession handling

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    print("NSURLSession finished to url: ", location)
    updateDateLabel()
    scheduleSnapshot()

    self.pendingBackgroundURLTask.setTaskCompleted()
    self.backgroundUrlSession = nil
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if error != nil {
        self.pendingBackgroundURLTask.setTaskCompleted()
        self.backgroundUrlSession = nil
    }
}

Also 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. I've updated my answer to include this logic as well.

Hope this helps you out. It's working in production for us.