Can't get WatchKit URLSession background to wo

2019-08-18 14:39发布

问题:

I have a file for a BackgroundSession class

class BackgroundSession: NSObject {
  static let shared = BackgroundSession()

  static let identifier = "com.***.bg"

  private var session: URLSession!

  var savedCompletionHandler: (() -> Void)?

  private override init() {
    super.init()

    let configuration = URLSessionConfiguration.background(withIdentifier: BackgroundSession.identifier)
    session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
  }

  func start(_ request: URLRequest) {
    session.downloadTask(with: request).resume()
  }
}

extension BackgroundSession: URLSessionDelegate {
  func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    DispatchQueue.main.async {
      self.savedCompletionHandler?()
      self.savedCompletionHandler = nil
    }
  }
}

extension BackgroundSession: URLSessionTaskDelegate {
  func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error {
      // handle failure here
      print("\(error.localizedDescription)")
    }
  }
}

extension BackgroundSession: URLSessionDownloadDelegate {
  func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    do {
      let data = try Data(contentsOf: location)
      let json = try JSONSerialization.jsonObject(with: data)

      print("\(json)")
      // do something with json
    } catch {
      print("\(error.localizedDescription)")
    }
  }
}

I am listening for background location updates to come in later on

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    print("didUpdateLocations")
    if locations.first != nil {
      let lastLocation = locations.last
      self.lastLocation = lastLocation
      print("doing background work")
      self.getUserData()
      if PubnubController.pubnubChannel != nil {
        PubnubController.sharedClient.publish(["action": "newCoordinates", "data": ["coordinates": ["latitude": lastLocation?.coordinate.latitude, "longitude": lastLocation?.coordinate.longitude]]], toChannel: PubnubController.pubnubChannel!, compressed: false)
      }
    }
  }

self.getUserData() looks like this

func getUserData() {
    print("getUserData")
    if (self.userId != -1 && self.userAuthToken != nil) {
      let httpUrl: String = "https://api.***.com/dev/users/\(self.userId)"
      guard let url = URL(string: httpUrl) else {
        return
      }
      var request = URLRequest(url: url)
      request.setValue(self.userAuthToken, forHTTPHeaderField: "Authorization")
      let session = BackgroundSession.shared
      session.start(request)
    }
  }

In my ExtensionDelegate.swift I have the typical func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)

with a for loop and switch set with a case for WKURLSessionRefreshBackgroundTask that looks like this

case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
              print("WKURLSessionRefreshBackgroundTask")
                // Be sure to complete the URL session task once you’re done.
                urlSessionTask.setTaskCompletedWithSnapshot(false)

In my controller, I also have pasted the function the class is supposed to call

func application(_ application: WKExtension, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    print("handleEventsForBackgroundURLSession")
    BackgroundSession.shared.savedCompletionHandler = parseUserData
  }

It seems that both the delegate function and this pasted function are not being called with my data. I'm having a really hard time trying to understand this background URLSession flow

Note the BackgroundSession class came from this Stackoverflow question

URLSession.datatask with request block not called in background

回答1:

This handleEventsForBackgroundURLSession is an iOS pattern. This is a method of the UIApplicationDelegate protocol. You can’t just add that to some random controller. It’s only applicable for your iOS app's UIApplicationDelegate.

For watchOS, I suspect the idea is the same, except rather than calling the completion handler that iOS provides, supply your own completion handler to the BackgroundSession that calls the setTaskCompletedWithSnapshot of the WKURLSessionRefreshBackgroundTask:

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
    for task in backgroundTasks {
        // Use a switch statement to check the task type
        switch task {
        case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
            // Be sure to complete the URL session task once you’re done.
            BackgroundSession.shared.savedCompletionHandler = {
                urlSessionTask.setTaskCompletedWithSnapshot(false)
            }
        ...
        }
    }
}

But, effectively, the idea is the same. We’re deferring the setTaskCompletedWithSnapshot until urlSessionDidFinishEvents(forBackgroundURLSession:) is called.


If you want the BackgroundSession to call your controller’s parser, you can specify a protocol for that interface:

protocol Parser: class {
    func parse(_ data: Data)
}

You can then give your BackgroundSession a property to keep track of the parser:

weak var parser: Parser?

You can have the didFinishDownloadingTo call the parser:

extension BackgroundSession: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        do {
            let data = try Data(contentsOf: location)
            parser?.parse(data)
        } catch {
            os_log(.error, log: log, "Error retrieving data for %{public}@: %{public}@", downloadTask.originalRequest?.url?.absoluteString ?? "Unknown request", error.localizedDescription)
        }
    }
}

You can then have your controller (or whatever) (a) conform to this protocol; (b) implement the parse(_:) method of that protocol; and (c) specify itself as the parser:

BackgroundSession.shared.parser = self