URLSession.datatask with request block not called in background
URLSession
The data task block is not called when the application is in the background and it gets stuck dataTask
with a request.
When I open the application, the block is called. By the way, I'm using query https
.
This is my code:
let request = NSMutableURLRequest(url: URL(string: url as String)!,
cachePolicy: .reloadIgnoringCacheData,
timeoutInterval:20)
request.httpMethod = method as String
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let data = params.data(using: String.Encoding.utf8.rawValue)
request.httpBody = data
session.dataTask(with: request as URLRequest,completionHandler:
{(data, response, error) -> Void in
if error == nil
{
do {
let result = try JSONSerialization.jsonObject(with: data!, options:
JSONSerialization.ReadingOptions.mutableContainers)
print(result)
completionHandler(result as AnyObject?,nil)
}
catch let JSONError as NSError{
completionHandler(nil,JSONError.localizedDescription as NSString?)
}
}
else{
completionHandler(nil,error!.localizedDescription as NSString?)
}
}).resume()
Works great when the app is active. There is something wrong in my code. please point me
source to share
If you want the download to continue after your app is no longer in the foreground, you must use a background session. The main limitations of background sessions are described in Downloading files in the background , and in essence:
Use delegate based
URLSession
with backgroundURLSessionConfiguration
.Use only load and unload tasks without completion handlers.
On iOS, inject the
application(_:handleEventsForBackgroundURLSession:completionHandler:)
app delegate while keeping the completion handler and starting a background session.Implement
urlSessionDidFinishEvents(forBackgroundURLSession:)
in yoursURLSessionDelegate
by invoking the stored completion handler so that the OS knows that you have finished processing the background request completion.
So, putting this 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 }
source to share
[URLSessionDownloadTask setDownloadTaskDidWriteDataBlock:^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
CGFloat percentDone = (double)(totalBytesWritten)/(double)totalBytesExpectedToWrite;
[SVProgressHUD showWithStatus:[NSString stringWithFormat:@"%.2f%%",percentDone*100]];
}];
[downloadTask resume];
// Apply as shown in the picture
source to share
@Rob thanks for your answer. How can I get mine json
instead of print("\(json)")
in startRequest
? Because I need to send to another class to handle if its an error, or if its data, convert it to my model correctly. I also tried your code from this link , but my background code stops at guard let data = data, error == nil else {
and just returns if I open it (I don't want that). PS: Sorry SO does not allow me to add a comment to your answer
source to share