Documente Academic
Documente Profesional
Documente Cultură
19/12/16, 1(57 AM
Getting Started
Download the starter project; it already contains a user interface
for searching for songs and displaying search results, as well as
some helper methods to parse JSON and play tracks. This lets
you focus on implementing the networking aspects of the app.
Build and run your project; you should see a view with a search
bar at the top and an empty table view below:
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 1 of 17
19/12/16, 1(57 AM
Type a query in the search bar and tap Search. The view remains empty, but dont worry; youll change this with your
new NSURLSession calls.
Overview of NSURLSession
Before you begin, its important to appreciate NSURLSession and its constituent classes, so take a minute to walk
through the quick overview below.
NSURLSession is technically both a class and a suite of classes for handling HTTP/HTTPS-based requests:
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 2 of 17
19/12/16, 1(57 AM
NSURLSession is the key object responsible for sending and receiving HTTP requests. You create it via
NSURLSessionConfiguration
NSURLSessionConfiguration, which comes in three flavors:
defaultSessionConfiguration
defaultSessionConfiguration: Creates a default configuration object that uses the disk-persisted global cache, credential and cookie storage objects.
ephemeralSessionConfiguration
ephemeralSessionConfiguration: Similar to the default configuration, except that all session-related
data is stored in memory. Think of this as a private session.
backgroundSessionConfiguration
backgroundSessionConfiguration: Lets the session perform upload or download tasks in the background. Transfers continue even when the app itself is suspended or terminated.
NSURLSessionConfiguration also lets you configure session properties such as timeout values, caching policies and additional HTTP headers. Refer to the documentation for a full list of configuration options.
NSURLSessionTask is an abstract class that denotes a task object. A session creates a task, which does the actual
work of fetching data and downloading or uploading files.
There are three types of concrete session tasks in this context:
NSURLSessionDataTask
NSURLSessionDataTask: Use this task for HTTP GET requests to retrieve data from servers to memory.
NSURLSessionUploadTask
NSURLSessionUploadTask: Use this task to upload a file from disk to a web service, typically via a HTTP
POST or PUT method.
NSURLSessionDownloadTask
NSURLSessionDownloadTask: Use this task to download a file from a remote service to a temporary file
location.
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 3 of 17
19/12/16, 1(57 AM
You can also suspend, resume and cancel tasks. NSURLSessionDownloadTask has the additional ability to
pause for future resumption.
Generally, NSURLSession returns data in two ways: via a completion handler when a task finishes either successfully or with an error, or by calling methods on a delegate that you set upon session creation.
Now that you have an overview of what NSURLSession can do, youre ready to put the theory into practice!
// 1
let defaultSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
// 2
var dataTask: NSURLSessionDataTask?
Heres what youre doing in the above code:
1. You create a NSURLSession and initialize it with a default session configuration.
2. You declare a NSURLSessionDataTask variable which youll used to make an HTTP GET request to the
iTunes Search web service when the user performs a search. The data task will be re-initialized and reused each
time the user creates a new query.
Now, replace searchBarSearchButtonClicked(_:) with the following:
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
dismissKeyboard()
if !searchBar.text!.isEmpty {
// 1
if dataTask != nil {
dataTask?.cancel()
}
// 2
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 4 of 17
19/12/16, 1(57 AM
// 3
let expectedCharSet = NSCharacterSet.URLQueryAllowedCharacterSet()
let searchTerm =
searchBar.text!.stringByAddingPercentEncodingWithAllowedCharacters(expectedCharSet)!
// 4
let url = NSURL(string: "https://itunes.apple.com/search?media=music&entity=song&term=\(searchTerm)")
// 5
dataTask = defaultSession.dataTaskWithURL(url!) {
data, response, error in
// 6
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
// 7
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
self.updateSearchResults(data)
}
}
}
// 8
dataTask?.resume()
}
}
Taking each numbered comment in turn:
1. Upon each user query, you check if the data task is already initialized. If so, you cancel the task as you want to
reuse the data task object for the latest query.
2. You enable the network activity indicator on the status bar to indicate to the user that a network process is
running.
3. Before passing the users search string as a parameter to the query URL, you call stringByAddingPercentEncodingWithAllowedCharacters(_:) on the string to ensure that its properly escaped.
4. Next you construct a NSURL by appending the escaped search string as a GET parameter to the iTunes Search
API base url.
5. From the session you created, you initialize a NSURLSessionDataTask to handle the HTTP GET request. The
constructor of NSURLSessionDataTask takes in the NSURL that you constructed along with a completion
handler to be called when the data task is completed.
6. Upon receiving a callback that the task completed, you hide the activity indicator and invoke the UI update in the
main thread.
7. If the HTTP request is successful, you call updateSearchResults(_:)
updateSearchResults(_:), which parses the response NSData into Track
Tracks and updates the table view.
8. All tasks start in a suspended state by default; calling resume() starts the data task.
Build and run your app; search for any song and you should see the table view populate with the relevant track results like so:
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 5 of 17
19/12/16, 1(57 AM
With a little bit of NSURLSession magic added, Half Tunes is now somewhat functional!
Downloading a Track
Being able to view song results is nice, but wouldnt it be better if you could tap on a song to download it? Thats precisely your next order of business.
To make it easy to handle multiple downloads, youll first create a custom object to hold the state of an active
download.
Create a new file named Download.swift in the Data Objects group.
Open Download.swift and add the following implementation:
class Download: NSObject {
var url: String
var isDownloading = false
var progress: Float = 0.0
var downloadTask: NSURLSessionDownloadTask?
var resumeData: NSData?
init(url: String) {
self.url = url
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 6 of 17
19/12/16, 1(57 AM
}
}
Heres a rundown of the properties of Download
Download:
url: The URL of the file to download. This also acts as a unique identifier for a Download
Download.
isDownloading: Whether the download is ongoing or paused.
progress: The fractional progress of the download; a float between 0.0 and 1.0.
downloadTask: The NSURLSessionDownloadTask that downloads the file.
resumeData: Stores the NSData produced when you pause a download task. If the host server supports it, you
can use this to resume a paused download in the future.
Switch to SearchViewController.swift and add the following code to the top of your class:
var activeDownloads = [String: Download]()
This simply maintains a mapping between URLs and their active Download
Download, if any.
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 7 of 17
19/12/16, 1(57 AM
With your session and delegate configured, youre finally ready to create a download task when the user requests a
track download.
In SearchViewController.swift, replace startDownload(_:) with the following implementation:
func startDownload(track: Track) {
if let urlString = track.previewUrl, url = NSURL(string: urlString) {
// 1
let download = Download(url: urlString)
// 2
download.downloadTask = downloadsSession.downloadTaskWithURL(url)
// 3
download.downloadTask!.resume()
// 4
download.isDownloading = true
// 5
activeDownloads[download.url] = download
}
}
When you tap the Download button for a track, you call startDownload(_:) with the corresponding Track
Track.
Heres whats going on :
1. You first initialize a Download with the preview URL of the track.
2. Using your new session object, you create a NSURLSessionDownloadTask with the preview URL, and set it
to the downloadTask property of the Download
Download.
3. You start the download task by calling resume() on it.
4. You indicate that the download is in progress.
5. Finally, you map the download URL to its Download in the activeDownloads dictionary.
Build and run your app; search for any track and tap the Download button on a cell. You should see a message printed on your console after a while, signifying that the download is complete.
Page 8 of 17
19/12/16, 1(57 AM
// 2
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.removeItemAtURL(destinationURL)
} catch {
// Non-fatal: file probably doesn't exist
}
do {
try fileManager.copyItemAtURL(location, toURL: destinationURL)
} catch let error as NSError {
print("Could not copy file to disk: \(error.localizedDescription)")
}
// 3
if let url = downloadTask.originalRequest?.URL?.absoluteString {
activeDownloads[url] = nil
// 4
if let trackIndex = trackIndexForDownloadTask(downloadTask) {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: trackIndex,
inSection: 0)], withRowAnimation: .None)
})
}
}
}
Heres the key steps from above:
1. You extract the original request URL from the task and pass it to the provided localFilePathForUrl(_:)
helper method. localFilePathForUrl(_:) then generates a permanent local file path to save to by appending the lastPathComponent of the URL (i.e. the file name and extension of the file) to the path of the
apps Documents directory.
2. Using NSFileManager
NSFileManager, you move the downloaded file from its temporary file location to the desired destination file path by clearing out any item at that location before you start the copy task.
3. You look up the corresponding Download in your active downloads and remove it.
4. Finally, you look up the Track in your table view and reload the corresponding cell.
Build and run your project; pick any track and download it. When the download has finished, you should see the file
path location printed to your console:
The Download button will also disappear, since the track is now on your device. Tap the track and youll hear it play in
the presented MPMoviePlayerViewController as shown below:
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 9 of 17
19/12/16, 1(57 AM
// 1
if let downloadUrl = downloadTask.originalRequest?.URL?.absoluteString,
download = activeDownloads[downloadUrl] {
// 2
download.progress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
// 3
let totalSize = NSByteCountFormatter.stringFromByteCount(totalBytesExpectedToWrite, countStyle: NSByteCountFormatterCountStyle.Binary)
// 4
if let trackIndex = trackIndexForDownloadTask(downloadTask), let trackCell =
tableView.cellForRowAtIndexPath(NSIndexPath(forRow: trackIndex, inSection: 0)) as?
TrackCell {
dispatch_async(dispatch_get_main_queue(), {
trackCell.progressView.progress = download.progress
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 10 of 17
trackCell.progressLabel.text =
.progress * 100, totalSize)
})
}
}
}
19/12/16, 1(57 AM
download-
}
cell.progressView.hidden = !showDownloadControls
cell.progressLabel.hidden = !showDownloadControls
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 11 of 17
19/12/16, 1(57 AM
Note: You can only resume a download under certain conditions. For instance, the resource must not have
changed since you first requested it. For a full list of conditions, check out the Apple documentation here.
Page 12 of 17
19/12/16, 1(57 AM
With the pause function completed, the next order of business is to allow the resumption of a paused download.
Replace resumeDownload(_:) with the following code:
func resumeDownload(track: Track) {
if let urlString = track.previewUrl,
download = activeDownloads[urlString] {
if let resumeData = download.resumeData {
download.downloadTask = downloadsSession.downloadTaskWithResumeData(resumeData)
download.downloadTask!.resume()
download.isDownloading = true
} else if let url = NSURL(string: download.url) {
download.downloadTask = downloadsSession.downloadTaskWithURL(url)
download.downloadTask!.resume()
download.isDownloading = true
}
}
}
When the user resumes a download, you check the appropriate Download for the presence of resume data. If
found, you create a new download task by invoking downloadTaskWithResumeData(_:) with the resume
data and start the task by calling resume()
resume(). If the resume data is absent for some reason, you create a new download task from scratch with the download URL anyway and start it.
In both cases, you set the isDownloading flag of the Download to true to indicate the download has resumed.
Theres only one thing left to do for the three functions to work properly: you need to show or hide the Pause, Cancel
and Resume buttons as appropriate.
Go to tableView(_:cellForRowAtIndexPath:) and find the following line of code:
if let download = activeDownloads[track.previewUrl!] {
Add the following lines to the end of the let block above:
let title = (download.isDownloading) ? "Pause" : "Resume"
cell.pauseButton.setTitle(title, forState: UIControlState.Normal)
Since the pause and resume functions share the same button, the code above toggles the button between the two
states as appropriate.
Next, add the following code to the end of tableView(_:cellForRowAtIndexPath:)
tableView(_:cellForRowAtIndexPath:), just before the return statement:
cell.pauseButton.hidden = !showDownloadControls
cell.cancelButton.hidden = !showDownloadControls
Here you simply show the buttons for a cell only if a download is active.
Build and run your project; download a few tracks concurrently and youll be able to pause, resume and cancel them
at will:
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 13 of 17
19/12/16, 1(57 AM
Note: If you terminate the app by force-quiting from the app switcher, the system will cancel all of the sessions
background transfers and wont attempt to relaunch the app.
Page 14 of 17
19/12/16, 1(57 AM
The above code simply grabs the stored completion handler from the app delegate and invokes it on the main
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 15 of 17
19/12/16, 1(57 AM
thread.
Build and run your app; start a few concurrent downloads and tap the Home button to background the app. Wait until you think the downloads have completed, then double-tap the Home button to reveal the app switcher.
The downloads should have finished and their new status reflected in the app screenshot. Open the app to confirm
this:
You now have a fully functional music streaming app! Your move now, Apple Music! :]
Page 16 of 17
19/12/16, 1(57 AM
tutorial.
I hope you found this tutorial useful. Feel free to join the discussion below!
Ken Toh
Ken Toh is a software engineer from Singapore with more than 5 years of experience in iOS
development. During his free time, he likes to work on various side projects. He has also
launched several apps on the AppStore, namely RPS Mutants, Where's My Tea? and SG
Libraries.
When he is not coding, he enjoys reading and playing football.
You can follow him on Twitter or visit his website at kentoh.com.
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
Page 17 of 17