Part 5 — Networking with URLSession
In this article, we’re going to see how we can fetch data from an API by using URLSession.
IECSE Crash Course: Development with Swift
This article is going to be the fifth article of the series IECSE Crash Course: Development with Swift. Through the course of 10 articles, we are going to make a small app which will display a set of users that we will fetch from the net. While making the app, we will cover some of the most important topics that every iOS developer must know.
In the last tutorial, we saw how to make structures for the data that we are going to receive from the API and we have this structure ready in our project. If you haven’t been following the whole course, I suggest you go through the code here and clone it.
We’ve made this structure according to what “https://jsonplaceholder.typicode.com/users" sends to us. Now we are going to try and get the User data from here and display it on our app.
Networking and REST APIs
Whatever data we see in the apps we use comes from the internet. This is where networking and APIs come in. This exchange of data between the device and a server is called networking. REST is basically a type of architecture that allows us to create services and applications that can be used by any device or client that understands HTTP. For example in our case, we will make a get request on https://jsonplaceholder.typicode.com/users
and this will return to us the data in the form of a JSON Object. Knowing this much is enough for us right now but if you want to understand the REST APIs a little better, give this a read.
URLSession
Since iOS 7, the default way of making an HTTP Networking Request is by using a class URLSession. The URLSession class is actually part of a group of classes that work together to make and respond to HTTP requests.
URLSession includes a number of core classes. This is how the environment works:
- You use URLSession to create a session. You can think of a session as an open tab or window in your web browser, which groups together many HTTP requests over subsequent website visits.
- The URLSession is used to create
URLSessionTask
instances, which can fetch and return data to your app, and download and upload files to webservices. - You configure a session with a
URLSessionConfiguration
object. This configuration manages caching, cookies, connectivity and credentials. - To make a request, you create a data task, of class
URLSessionDataTask
, and you provide it with a URL, and a completion handler. This is a closure that’s executed when the request’s response is returned to your app. (We’ll talk about closures in a while) - When the completion handler is executed, you can inspect the returned data and take appropriate action, such as loading the data into the TableView, like we will be doing in a while.
We can think of URLSession as a factory that produces certain URLSessionTask and each task is responsible for a networking request.
We can make 3 kinds of requests with URLSession:
- Data tasks: send and receive data with URLSessionDataTask, by using NSData objects. They’re the most common for webservice requests, for example when working with JSON.
- Upload tasks: send files to a webserver with URLSessionUploadTask. They’re similar to data tasks, but URLSessionUploadTask instances can also upload data in the background (or when an app is suspended).
- Download tasks: download files from a webserver with URLSessionDownloadTask by directly writing to a temporary file. You can track the progress of file downloads, and pause and resume them.
We’ll look at the rest of the theory as and when we need it. So, let’s get back to building our app!
First, we make a new file “Networking.swift”. This file will be a networking layer i.e. this is the file where we will make all the networking calls for out app.
Let’s set up a few things in this file, before we make our GET request.
When you look at this code the first time, it seems broken. One might wonder how can we make sharedInstance
an object of structure Networking , when it itself is inside the struct Networking. And you are not wrong to be thinking that. But what we have written here, is called a Singleton
. A singleton is an object that should only be created once and then be shared everywhere it has to be used. So, the struct Networking creates an instance of itself as a static variable, which means the only instance of the Networking struct is the one it created: Networking.sharedInstance
. This is the only instance of Networking that will be used throughout our app to make networking calls.
The next line, let session = URLSession.shared
, with this we get a reference to URLSession by invoking shared of URLSession class. This returns a shared session object, which again is a singleton.
In the last line the URL, we are going to make the get request. Before we make the final networking call, we are going to try and understand all the other basics that make understanding the network call very easy.
Closures And Completion Handlers
Closures are self-contained blocks of functionality that can be passed around and used in your code. What this means is that you can pass functions as an argument to other functions. The function that you pass may be used at some point of time by the function that it is passed to. These can also be considered as Nested Functions.
Completion Handlers can be thought of as the closures that are executed once the function’s body is executed, i.e. Whenever a function with a completion handler finishes (the isFinished property of the function changes to true), the completion handler code is executed. It is very useful when dealing with functions which can take quite a bit of time, such as network calls.
@escaping
When you define a closure as @escaping, you want the closure to be preserved into memory. As you want the closure to be executed once the function with the closure has been executed. Like here, we want the closure to be executed once we have received the JSON data from the API.
For a better understanding of closures, visit here.
We’ll understand this better once we look at the code. Let’s have a look at the syntax for making a networking call.
Now when we look at the function getUsers, we have defined an escaping closure in this code, ie this gets executed when the getUsers has been executed. The completion handler here returns us Result.
Let’s take a second and understand what Result means over here. With Swift 5.1, Apple introduced Result type in its library, it was basically used to deal with ambiguity when we are waiting for the data to be received from an API. At this point we do not know whether data was received or if it is going to return an error, this is where Result comes in.
Result type is implemented as enum, it has two cases,
- .success, this helps you in returning the data that was received.
- .failure, this helps you define and return proper errors.
Here, Result will return an array of Users if the function call was successful or a user defined error if there was a failure.
Next, let’s try and understand what is happening inside the function.
guard let UserURL = URL(string: userURL) else { ..}
, here the guard statement helps us deal with errors, i.e. if the string could not be converted to a URL, only then the else block is executed and this is where we throw an error.- When you use guard else statements, make sure that we return something or exit the scope. As execution cannot be continued if the condition associated with it is false or cannot be executed.
- In this, we are just converting our string to a URL. If the string is not converted we throw an error, however this will never happen for us as we know the string that we have defined will work fine.
- It is the next line where we use URLSession and create a dataTask with the URL, this is going to return data to us as JSON if it is successful, otherwise, it’ll give us an error. Hence, we try and unwrap this JSON data, and we call this jsonData, if this fails and there is no data to unwrap, we return the Result as “.failure” with the user defined error.
- If the above line did not result in an error, this means that we now have the data that the API sent as JSON, now we need to parse it and return the array of type User as Result “.success”. We do this with the help of JSONDecoder.
- If a failure occurs here, we return the error again.
- Last but not least, we schedule the data task by invoking resume() on the data task. This is often overlooked by developers that are new to URLSession, but your network call will not get executed if resume is not invoked.
With this, our Network call is done and when we call the function getUsers, we will either get the array of type User or the appropriate error.
Let’s see the results
Finally, we are going to make a few changes in TableViewController.
Let's try break down and understand what's happening here.
- Dispatch Queues basically are queues to which our apps submit tasks that have to be carried out. Here, where initially we were defining an empty array, now we pass a closure that checks if the array was set, in case it is then it asks the Main DispatchQueue to reload the tableView so that the newly fetched content can be displayed.
- func fetchData is the function where we make the network call and receive either the array of type User or the error.
- We make the function call as “Networking.sharedInstance.getUsers{..}”, where we receive the result. The result will always have to be of type [weak self], as the closure we sent was @escaping, we check the type of result with switch case, if its success we set our array Users to the array that is returned by the network call.
- Here, we use weak self so that once the Network call is made, our app does not hold any references to Networking as we won’t be needing that class once the data has already been loaded. This basically takes care of memory leaks.
- We call the function fetchData() once the view is loaded in the window.
Now, if you run the app, you should get the data that we have received.
Wrapping Up
In this article, we have learned to fetch data from an API, and display them in our apps. You can continue with the series in the next article where we play with tab bars and navigation controls.
Congratulations 🎉! You have just finished the fifth tutorial!!
All of the above-written code is available on Github. Watch this space for more interesting challenges, up next in this series!
Confused about something? Let us know in the responses below. 😄