Urlsession.Datatask with Request Block Not Called in Background

URLSession.datatask with request block not called in background

If you want downloads to progress after your app is no longer in foreground, you have to use background session. The basic constraints of background sessions are outlined in Downloading Files in Background, and are essentially:

  1. Use delegate-based URLSession with background URLSessionConfiguration.

  2. Use upload and download tasks only, with no completion handlers.

  3. In iOS, Implement application(_:handleEventsForBackgroundURLSession:completionHandler:) app delegate, saving the completion handler and starting your background session.

    Implement urlSessionDidFinishEvents(forBackgroundURLSession:) in your URLSessionDelegate, calling that saved completion handler to let OS know you're done processing the background request completion.

So, pulling that together:

func startRequest(for urlString: String, method: String, parameters: String) {
let url = URL(string: urlString)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
request.httpMethod = method
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = parameters.data(using: .utf8)
BackgroundSession.shared.start(request)
}

Where

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

static let identifier = "com.domain.app.bg"

private var session: URLSession!

#if !os(macOS)
var savedCompletionHandler: (() -> Void)?
#endif

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 {
#if !os(macOS)
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
#endif
}

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

And the iOS app delegate does:

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

Swift background data task

you will need to implement AppDelegate method

  • application(_:handleEventsForBackgroundURLSession:completionHandler:)

and this into your delegate

  • urlSessionDidFinishEvents(forBackgroundURLSession:)

Also, you should implement background modes. A relative answer to your question could be found here. URLSession.datatask with request block not called in background

Does NSURLSession for HTTP data task (NSURLSessionDataTask) runs in background thread or we will have to provide the queue?

No, you don't need to use GCD to dispatch this to background queue. In fact, because the completion block runs on background thread, the exact opposite is true, that if you need anything in that block to run on the main queue (e.g., synchronized updates to model objects, UI updates, etc.), you have to manually dispatch that to the main queue yourself. For example, let's imagine that you were going to retrieve a list of results and update the UI to reflect this, you might see something like:

- (void)viewDidLoad 
{
[super viewDidLoad];

NSURLSession *session = [NSURLSession sharedSession];

NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// this runs on background thread

NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];

// detect and handle errors here

// otherwise proceed with updating model and UI

dispatch_async(dispatch_get_main_queue(), ^{
self.searchResults = json[@"results"]; // update model objects on main thread
[self.tableView reloadData]; // also update UI on main thread
});

NSLog(@"%@", json);
}];

[dataTask resume];
}


Related Topics



Leave a reply



Submit