Fetching and processing data between an app and a web service is an essential skill that all iOS developers need to master.
The most commonly-used protocol for communicating with web servers is HTTP (and its secure counterpart, HTTPS).
Since iOS 7, Apple has provided URLSession and its suite of related components as a complete networking API for uploading or downloading content via HTTP and HTTPS.
At the end of this class, you should be able to...
- Implement a simple HTTP-based request to a public Internet API using the primary components of URLSession.
- Validate HTTP response data and guard against common HTTP error conditions.
- Display text and images fetched from free web services.
Like most networking APIs, the URLSession API is highly asynchronous.
Every URLSession instance is initialized using an URLSessionConfiguration
object, which defines the behavior and policies to use when uploading and downloading data.
URLSessionConfiguration
will also let you configure additional HTTP headers, timeout values, caching policies, and other session properties.
URLSessionConfiguration objects come in 3 flavors:
- .default - Creates a default configuration object that uses the disk-persisted global cache, credential and cookie storage objects. Can save cache or cookies to disk, credentials to the Keychain.
- .ephemeral - Similar to
.default
, but all session-related data is stored in memory and will be gone once the session terminates.
- .background - Allows the session to perform upload or download tasks in the background, even if the app is suspended.
Here is a simple example of a declaration of a URLSession
instance using the .default
URLSessionConfiguration
type:
let defaultSession = URLSession(configuration: .default)
To do the real work of fetching data or downloading/uploading files, a session creates one or more tasks.
URLSessionTask
is the class that contains predefined network task behaviors.
The three concrete subclasses of URLSessionTask which you will employ most often are:
- URLSessionDataTask - Intended for short, often interactive server requests such as fetching data by sending HTTP requests (GET, POST, etc). Data tasks send and receive data using
NSData
objects.
- URLSessionUploadTask - Similar to data tasks, but also send data (such as a file) from disk to web service, and they support background uploads while the app is no longer running.
- URLSessionDownloadTask - Data from a remote service is retrieved in the form of a file and stored in a temporary location. Supports background downloads/uploads while app the isn't running.
Instructions here
Except for handling the response, this code snippet depicts the complete set of steps required to make our simple GET request:
func someDataFetchFunction() {
// Configure a .default session
let defaultSession = URLSession(configuration: .default)
// Create URL
let url = URL(string: "https://<your_target_web_service>")
// Create Request
let request = URLRequest(url: url!)
// Create Data Task
let dataTask = defaultSession.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
// handle Response Here
})
dataTask.resume() // Start the data task
}
When making network requests with HTTP/S, success or failure can occur at several levels:
- Any errors with the Response?
- Was the expected HTTP Status Code returned?
- Was data returned in the correct format?
- Was a data object returned at all?
Handling errors and validating successful state are key to working with the URLSession family of classes and functions.
Developer Hints:
- Whether or not all the procedures listed are required or optional depends on your particular implementation of URLSession.
- However, handling the error returned and converting JSON data should always be considered as required steps .
Check if the error
object is nil.
If not, properly handle the error (for now, we'll simply print the error returned):
// guard against any errors with this HTTP response
guard error == nil else {
print ("error: ", error!)
return
}
Ensure that a data
object has been returned with the response:
// protect against no data returned from HTTP response...
guard data != nil else {
print("No data object")
return
}
Confirm that the HTTP Status Code returned falls within the range of acceptable codes. If not, we want to properly respond to the error condition:
// Confirm the HTTP Status Code is within the range of acceptable ones
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("response is: ", response!)
return
}
Now that we know the response status is good, we can validate the format of the returned data.
The MIME Type,
a value returned by most web servers, tells us the format of the returned data. We want to ensure that the data returned is in the expected format (JSON, in this case):
// Validate response data is in expected format
guard let mime = response?.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
Practicing requests with the Pokemon API + UI
URLSession
objects are asynchronous. Research what that means and be able to answer the following question:
- Why do we need to add the following call to
main.async
when presenting an image from a network call:
DispatchQueue.main.async {
//A downloaded image placed into an imageView
}