Extend URLSession and never forget .resume() again
By: Kenny Dubroff
Tags: Urlsession, Swift, Datatask, Xcode

No more .resume() — Use Mock Data as you please — Or Live data if you need

Extend URLSession and never forget .resume() again

Also Introducing a protocol that will allow you to switch between live and mock data

Look ma, no .resume()

If you work with networking in Swift, chances are you’ve used URLSession. Chances are also that you’ve spent 1, 2 or a hundred minutes debugging a call that just wouldn’t fire! Then you sheepishly realized you forgot .resume() at the end of your dataTask…

Why do I even need .resume() ?

If you look carefully at the way we do a network call, we aren’t immediately doing anything with the code inside of our dataTask. The dataTask method returns a URLSessionDataTask object that you can use for Asynchronous operations.

URLSession.shared.dataTask returns something!

Once you call one of the methods on dataTask, things begin to happen. Focusing on our “culprit” here, the resume method…

URLSessionDataTask inherits from URLSessionTask which has our resume method:

When we first get the DataTask back, it’s in a suspended state…

Ok, now you know you have to .resume() in order to make the work you just put into the dataTask do anything. How can we avoid forgetting this in the future?

Protocols in Swift ¹

If you know anything about protocols in Swift, you probably know they’re basically a set of instructions that anything conforming to them has to follow. You may also know that you can extend a protocol to give it a default implementation. What we’re going to do here is create a protocol, extend URLSession, and provide default implementation.

The protocol

This very simple protocol is very powerful, allowing you to not only extend URLSession in order to never forget .resume() again, but it will also allow you to (with a little more work) create a Mock Data Loader that you can use in Unit testing to provide yourself with data that you know will never change! … more on that here. For now, here’s our protocol!

All that lead-up for… this?

protocol NetworkLoader { func loadData(using request: URLRequest, with completion: @escaping (Data?, URLResponse?, Error?) -> Void) }

If you look at that closely, you’ll notice it’s nearly identical to our dataTask method that requires .resume() — we’re essentially going to be using it in order to hijack dataTask and force it to resume() for us! … ok … that may have been a little overdramatic. No dataTasks were harmed in the writing of this article.

Ok, so let’s extend URLSession with our protocol, conform to our protocol (stub out the loadData method) and provide default implementation all in one swoop.

extension URLSession: NetworkLoader {



// call dataTask and resume, passing the completionHandler func loadData(using request: URLRequest, with completion: @escaping (Data?, URLResponse?, Error?) -> Void) { self.dataTask(with: request, completionHandler: completion).resume() }

All we’re doing there is calling URLSession.dataTask in the loadData method and passing the completion to it. So now loadData will complete with everything that dataTask completes with. Let’s use it to load shall we?

func loadMedium() { let url = URL(string: "https://www.medium.com")!

let request = URLRequest(url: url)

URLSession.shared.loadData(using: request) { (data, response, error) in //check for errors

if let error = error {

print("Error loading data!

\(error)")

return

} if let response = response as? HTTPURLResponse {

let statusCode = response.statusCode

//check to make sure medium.com responded to our request with a success code

if statusCode != 200 { print("\(url.absoluteString) returned bad status code: \(statusCode)!") } else {

print("Loading Medium.com successful!")

//print the page's html

guard let data = data,

let medium = String(data: data, encoding: .utf8)

else { return } print(medium) } } } }

There you have it, just implement that Protocol and call URLSession.shared.loadData from now on instead of URLSession.shared.dataTask … and never forget .resume() again!

Footnotes

1. A protocol is not required for this single use case. You could just extend URLSession and implement the loadData method. It can also be used to implement a MockDataLoader using the same method though — and that is very powerful for such a simple protocol