Skip to content
This repository has been archived by the owner on Aug 29, 2022. It is now read-only.

Working with URLSession

Zachary Waldowski edited this page Sep 3, 2018 · 3 revisions

There are many considerations for the networking stack in your application. Given the breadth of these considerations, we didn't want a one-size-fits-all route for doing networking with Deferred. For instance, using URLSessionDelegate disables the convenience callback methods like URLSession.dataTask(with:completionHandler:) that would typically be used by a solution built-in to Deferred.

With that caveat, the Task module to Deferred provides a great way to tie it all together.

Wrapping Completion Handlers

Beginning in iOS 11, macOS High Sierra, watchOS 4, and tvOS 11, URLSession has built-in progress handling that is compatible with Task.

extension URLSession {

    /// Returns the data from the contents of a URL, based on `request`, as a
    /// future.
    ///
    /// The session bypasses delegate methods for result delivery. Delegate
    /// methods for handling authentication challenges are still called.
    ///
    /// - parameter request: Object that provides the URL, body data, and so on.
    /// - parameter configure: An optional callback for setting properties on
    ///   the URL task.
    func beginDataTask(with request: URLRequest) -> Task<(Data, URLResponse)> {
        let deferred = Deferred<Task<(Data, URLResponse)>.Result>()
        let task = dataTask(with: request) { (data, response, error) in
            if let error = error {
                deferred.fail(with: error)
            } else if let data = data, let response = response {
                deferred.succeed(with: (data, response))
            } else {
                deferred.fail(with: URLError.badServerResponse)
            }
        }
        task.resume()
        return Task(deferred, progress: task.progress)
    }

}

Working with URLSessionDelegate

When maintaining your own session client, it is vital to learn that URLSession instances are self-contained, and all tasks within a session are unique by their taskIdentifier. This is useful for tracking constituent tasks. Consider the pseudocode below:

final class URLClient: NSObject, URLSessionTaskDelegate {

    private final class TaskState: NSObject {
        ...
    }

    private let urlSession: URLSession
    private var tasks = [Int: TaskState]()

    func URLSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
        tasks.removeAll()
    }

    func URLSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        guard let state = tasks.removeValue(forKey: task.taskIdentifier) else { return }

        // use `state`
    }

    func URLSession(_ session: URLSession, dataTask task: URLSessionDataTask, didReceive response: URLResponse, completionHandler: URLSessionResponseDisposition -> Void) {
        state[task.taskIdentifier] = TaskState(...)

        completionHandler(.allow)
    }

}
Clone this wiki locally