问题
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
来源:https://stackoverflow.com/questions/57433884/cant-get-watchkit-urlsession-background-to-work