Fetching Network Data

There are actually lots of APIs in iOS that can download data from a network connection. We’re going to focus on one that is the most useful for our current app and is the preferred networking API for most use cases: the URLSession. Like its name suggests, it’s a class for interacting with a URL for some period of time.

It’s also very flexible: URLSession can download or upload data, can work with password-protected resources, can download to disk or store in memory, and caches data it has already fetched (provided the server indicates that data is still valid). With proper care and feeding, a URLSession can download data while the app is in the background, so the app will have fresh content when foregrounded again.

We’ll start using URLSession by downloading a podcast feed and inspecting its contents. That’ll help us figure out what to do next.

Creating a New Class

We’re going to create a new class to download and parse podcast feeds, which brings up a question we’ve put off for a long time: How do we create new files in Xcode? So far, everything we’ve done has gone in the ViewController.swift that controls the player UI.

Of course, as a common task, creating files in Xcode is easy. Select ViewController.swift in the Project Navigator (1), just to ensure that our new file will be in the same group, under the PragmaticPodcasts folder icon. Now do FileNewFile…, or just N. This brings up a sheet of file templates.

images/network/new-file-templates-sheet.png

A set of small tabs at the top shows the SDK (iOS, watchOS, tvOS, macOS) we’re browsing; make sure it says “iOS.” The main part of the sheet shows different document types in sections: source files, user interface files, resources, and so on. Among source files, our most common options are to use a Cocoa Touch Class, if we know we’re subclassing something built into the iOS SDK, or Swift File, if we want an empty Swift file. Choose “Swift File” and click Next. A file-navigation sheet will fly in to let us name the file and specify where it should be saved. The default location is fine, so just give it the name PodcastFeedParser.swift, and click Create.

This will create the file, add it to the PragmaticPodcasts group, and select it for editing. Since we asked for a plain Swift file, it’s empty but for a header comment with a default copyright message, and the usual import Foundation directive.

Coding the New Class

So, how are we going to do this? We called our file PodcastFeedParser, so that implies we could take a podcast feed, parse its contents, and then let a caller access the results somehow. For technical reasons we’ll explain later, this will need to be a class (rather than, say, a struct with methods or extensions). So let’s start by stubbing out an empty class:

 class​ ​PodcastFeedParser​ {
 
 }

Now, since we will always be working with podcast feed URLs, let’s create an init method that takes a URL:

 init(contentsOf url: ​URL​) {

As a reminder, this syntax gives our init’s one parameter an outer name of contentsOf and an inner name of url. So callers will use let parser = PodcastFeedParser (contentsOf:someURL), while inside the initializer, we’ll work with url, not contentsOf. The idea is to make the inner and outer names appropriate to the contexts they’re used in.

Now how do we do anything with the URL? This is where the URLSession comes in. Take a look at its documentation and see that it has two initializers: one that takes a configuration, delegate, and delegateQueue, and another that only takes the configuration. We’ll use the easy one-parameter version for now, but do note the “queue” argument: this lets us determine which queue will execute any code we provide to the session. What’s interesting is that the behavior here is the opposite of what we saw in GCD and the Main Queue, with the AVPlayer; with URLSession, sending nil means “choose a background queue for me.” That’s good for keeping networking tasks from interfering with the main queue’s UI work, but it does mean we won’t be able to touch the UI directly in our network code.

Begin the init implementation by creating the URLSession, like this:

 let​ urlSession = ​URLSession​(configuration: .​default​)

This uses the default configuration for the session, as opposed to options better suited for things like downloading a URL directly to the filesystem.

Now that we have a session, what do we do with it? URLSessions let us create tasks to do things with them, like load data to memory or start streaming URL contents. There’s a whole separate URLDataTask class that we could set up, but there’s a convenience method that takes a URL to load and a closure to receive the results. The signature of the closure receives three optionals: a Data with the received data, an URLResponse with metadata like HTTP headers, and an Error if something went wrong.

Since the last argument is a closure, we can use the trailing-closure syntax to skip the closure’s variable label (completionHandler) and just start writing the call like this:

 let​ dataTask = urlSession.dataTask(with: url) {dataMb, responseMb, errorMb ​in

Putting Mb, for “maybe,” on the end of the variable names for the closure arguments is a convention we like for working with optionals, especially since the closure’s parameter syntax doesn’t include the type, so we don’t have a visual reminder that the first argument is a Data?, not just Data, the non-optional type.

This closure will be called when all the data from the URL has been received, or if we fail with an error. Let’s just consider the success case for now. If we used a valid URL and have network access, dataMb will be populated. Let’s try unwrapping it and see what we get:

 if​ ​let​ data = dataMb {
  print (​"PodcastFeedParser got data: ​​(​data​)​​"​)
  }
 }

That second closing curly brace ended our closure, completing the urlSession. dataTask(with: url){ … line. So we’ve used the URLSession to create a URLDataTask, and told the task what to do when it’s done. All that’s left now is to actually start the task. The superclass, URLSessionTask, defines all the start-stop sort of functionality, but there’s not actually a “start” method. Instead, since a task may be paused, we use the corresponding resume task to start or restart loading the URL. So we can finish our initializer like this:

  dataTask.resume()
 }

Now all we have to do is call this. Eventually, we’re going to load the podcast feed when the app starts up, so instead of going back to the player class, switch to the file AppDelegate.swift. This file was provided for us by Xcode and contains methods that are called at various points in an app’s life cycle: when it starts up, when it’s backgrounded or foregrounded, when it receives a push notification, etc.

Find the method application(didFinishLaunchingWithOptions:), and before its final return true, add the following lines:

 if​ ​let​ url = ​URL​(string: ​"http://cocoaconf.libsyn.com/rss"​) {
 let​ parser = ​PodcastFeedParser​(contentsOf: url)
 }

This looks to see if the provided string is a valid URL—feel free to copy over the URL from your favorite podcasts in iTunes, if you like. If it is valid, we use the URL to initialize the PodcastFeedParser class we just created. For the moment, we’re not going to do anything with parser, so don’t worry when the little yellow warning icon pops up to tell you that the value isn’t being used.

Run the app, and keep your eyes on the debug console (C). You should see our single print line’s output:

 PodcastFeedParser got data: 100373 bytes

OK, cool! We’re getting data! 100,373 bytes, in fact (or however much was in the URL you loaded). But it would be nice to actually see the podcast XML so we can figure out how to write the parser.

Data is not a type that says much about its contents. How could it? We could have downloaded text, images, media; all that Data says is that it has a bunch of bytes. Still, since we expect a podcast feed to have XML contents, we can try turning it into a string. There’s a String initializer that takes a Data, plus a hint as to how the data is encoded (ASCII, UTF-8, etc.). So back in PodcastFeedParser.swift, replace the print line with the following:

 if​ ​let​ dataString = ​String​(data: data, encoding: .utf8) {
  print (​"podcast feed contents: ​​(​dataString​)​​"​)
 }

Run the app again, and you’ll see a huge dump of XML in the console (we’ve cleaned it up a little for the book’s formatting):

 podcast feed contents: ​<?xml version="1.0" encoding="UTF-8"?>
 <rss version=​"2.0"​ xmlns:atom=​"http://www.w3.org/2005/Atom"
  xmlns:cc=​"http://web.resource.org/cc/"
  xmlns:itunes=​"http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:media=​"http://search.yahoo.com/mrss/"
  xmlns:rdf=​"http://www.w3.org/1999/02/22-rdf-syntax-ns#"​>
  <channel>
  <atom:link href=​"http://cocoaconf.libsyn.com/rss"​ rel=​"self"
  type=​"application/rss+xml"​/>
  <title>CocoaConf Podcast</title>
  <pubDate>Tue, 12 Jul 2016 15:43:32 +0000</pubDate>
  <lastBuildDate>Wed, 31 Aug 2016 05:50:12 +0000</lastBuildDate>
  <generator>Libsyn WebEngine 2.0</generator>
  <link>http://cocoaconf.com/podcast</link>
  <language>en-us</language>

…and so on. Great job! We’ve fetched our first podcast feed.

Now to figure out what’s in it!

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

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