Chapter 10.  Fetching and Displaying Data from the Network

Most modern applications communicate with a web service. Some apps rely on them heavily, acting as a layer that simply reads data from the Web and displays it in an app form. Other apps use the Web as a way to retrieve and sync data to make it locally available or only use the Web as a backup storage. Some apps are a combination of these multiple strategies.

In this chapter, we will expand the FamilyMovies application so it uses a web service to retrieve popularity ratings for the movies our family members have added as their favorites. We'll store these popularity ratings in our Core Data database and display them together with the names of the movies.

In this chapter, you'll learn about the following topics:

  • NSURLSession
  • Working with JSON in Swift
  • Updating Core Data objects with fetched data

By the end of this chapter, you'll be able to make good use of web services in your applications and you'll understand how you can combine a web service with Core Data to provide offline access and persistence of data that has been fetched from the Web.

Fetching data from the Web

Retrieving data from the Web is something that you will do often. You won't just fetch data, you'll also send data. For example, you might make an HTTP POST request to log in a user or to update their information. Over time, iOS has evolved quite a bit in the web requests department, making it easier, simpler, and more consistent to use the Web in our apps, and we're currently in a pretty good spot with the URLSession class.

The URLSession class makes asynchronous web requests on your behalf. This means that iOS will make the request on a background thread, ensuring that the user interface remains responsive throughout the duration of the entire web request. If the request was performed synchronously, the user interface would be unresponsive for the duration of the network request because a thread can only do one thing at a time, so if it's waiting for a response from the network, it can't respond to touches.

If your user has a slow Internet connection, a request could take several seconds. You don't want your interface to freeze for several seconds. Even a couple of milliseconds will create a noticeable drop in its responsiveness and frame rate. This can be easily avoided using URLSession to perform asynchronous network requests.

First, we will take a look at basic network requests in a playground. You can create a new playground or use the one provided in this book's GitHub repository. After you've seen the basics of URLSession, we'll implement a way to fetch movies from an open source movie database and finally put this implementation to use in the FamilyMovies app.

Understanding the URLSession basics

Let's take a look at a basic network request that simply loads the https://google.com homepage:

let url = URL(string: "https://www.google.com")! 
let task = URLSession.shared().dataTask(with: url) 
{ 
    data, response, error in 
    print(data) 
    print(response) 
    print(error) 
} 
 
task.resume() 

This is a very basic example; we created a URL and then we used the shared URLSession instance to create a new dataTask. This dataTask is an instance of URLSessionDataTask and allows us to load data from a remote server. Alternatively, we could use a download task if we're downloading an actual file or an upload task if we're uploading files to a web server. After creating the task, we will need to manually call resume on the task because new tasks are always created in the suspended state.

If you run this sample in a playground, you'll find that the sample doesn't work. Because our network request is made asynchronously, the playground finished its execution before the network request is complete. To fix this, we will need to make sure that the playground runs indefinitely. Doing so will allow the network request to finish. Add the following lines to the top of the playground source file to enable this behavior:

import XCPlayground 
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 

Now that the playground runs indefinitely, there isn't a lot of useful data printed to the console. We're not really interested in the raw data, HTTP headers, or the fact that the error is nil. The most interesting aspect of our fetch request is the actual text in the response body. We can retrieve this text by converting the raw data to a string. The following callback implementation demonstrates how this works:

{ data, response, error in 
    guard let data = data 
        where error == nil 
        else { return } 
 
    let responseString = String(data: data, 
                        encoding: String.Encoding.utf8) 
    print(responseString) 
} 

The preceding callback closure makes sure that there are no errors returned by the Web service and that there is data present. Then, we convert the raw data to a string and that string is printed to the console. If you use this callback instead of the old one, you'll see the HTML for the Google homepage printed. Simple requests to a web server are relatively simple to implement.

You can take some more control over the request that's executed by creating your own URLRequest instance. The example you saw before is one where you let the URLSession create the URLRequest on your behalf. This is fine if you want to perform a simple HTTP GET request with no custom headers, but if you'd want to post data or include certain headers, you will need to have more control over the request that's used. Let's take a look at what a GET request with some parameters and a custom header looks like in the following code.

The following code uses an API key from https://www.themoviedb.org/. If you want to try this code example, create an account on their website and request an API key on your account page. Setting this up should only take a couple of minutes, and if you want to follow along with this chapter, you will need to have your own API key:

let api_key = "YOUR_API_KEY_HERE" 
var urlString = "https://api.themoviedb.org/3/search/movie/" 
urlString = urlString.appending("?api_key=(api_key)") 
urlString = urlString.appending("&query=Swift") 
 
let movieURL = URL(string: urlString)! 
 
var urlRequest = URLRequest(url: movieURL) 
urlRequest.httpMethod = "GET" 
urlRequest.setValue("application/json", 
  forHTTPHeaderField: "Accept") 
 
let movieTask = URLSession.shared().dataTask(with: 
  urlRequest) { data, response, error in 
 
   print(response) 
} 
 
movieTask.resume() 

The preceding code is a bit more complex than what we saw before. We will set up a more complex URL that includes some HTTP GET parameters. We will also specify the httpMethod for the URLRequest and a custom header that informs the receiver of our request about the type of response we're willing to accept.

The flow for executing the fetch request is the same as we saw before. However, this API call responds with a JSON string instead of an HTML document. JSON is used by many APIs as the preferred format to pass data around on the Web. In order to use this response, we'll need to convert the raw data to a dictionary. We'll continue under the assumption that you are familiar with JSON as a format for data. If you haven't seen or worked with JSON before, it's a good idea to take a step back and read up on the JSON data format.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset