版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2019.06.15 星期六 |
前言
我们做APP发起网络请求,一般都是使用框架,这些框架的底层也都是苹果的API,接下来几篇就一起来看一下和网络有关的几个类。感兴趣的可以看上面几篇文章。
1. 详细解析几个和网络请求有关的类 (一) —— NSURLSession
2. 详细解析几个和网络请求有关的类(二) —— NSURLRequest和NSMutableURLRequest
3. 详细解析几个和网络请求有关的类(三) —— NSURLConnection
4. 详细解析几个和网络请求有关的类(四) —— NSURLSession和NSURLConnection的区别
5. 详细解析几个和网络请求有关的类(五) —— 关于NSURL加载系统(一)
6. 详细解析几个和网络请求有关的类(六) —— 使用NSURLSession(二)
7. 详细解析几个和网络请求有关的类(七) —— URL数据的编码和解码(三)
8. 详细解析几个和网络请求有关的类(八) —— 处理重定向和其他请求更改(四)
9. 详细解析几个和网络请求有关的类(九) —— 身份验证挑战和TLS链验证(五)
10. 详细解析几个和网络请求有关的类(十) —— 理解获取缓存(六)
11. 详细解析几个和网络请求有关的类(十一) —— Cookies和自定义协议(七)
12. 详细解析几个和网络请求有关的类(十二) —— URL Session的生命周期(八)
13. 详细解析几个和网络请求有关的类(十三) —— NSURLResponse(一)
14. 详细解析几个和网络请求有关的类(十四) —— NSHTTPCookie(一)
15. 详细解析几个和网络请求有关的类(十五) —— NSHTTPCookieStorage(一)
16. 详细解析几个和网络请求有关的类(十六) —— NSURLCache(一)
17. 详细解析几个和网络请求有关的类(十七) —— NSCachedURLResponse(一)
18. 详细解析几个和网络请求有关的类(十八) —— NSURLAuthenticationChallenge(一)
19. 详细解析几个和网络请求有关的类(十九) —— NSURLProtectionSpace(一)
20. 详细解析几个和网络请求有关的类(二十) —— NSURLCredential(一)
21. 详细解析几个和网络请求有关的类(二十一) —— NSURLCredentialStorage(一)
22. 详细解析几个和网络请求有关的类(二十二) —— NSStream(一)
23. 详细解析几个和网络请求有关的类(二十三) —— NSInputStream(一)
24. 详细解析几个和网络请求有关的类(二十四) —— NSOutputStream(一)
25. 详细解析几个和网络请求有关的类(二十五) —— NSHTTPCookie之设置删除和通信(二)
26. 详细解析几个和网络请求有关的类(二十六) —— NSURLError错误码及其相关作用(一)
27. 详细解析几个和网络请求有关的类(二十七) —— NSURLSession(三)
源码
1. Swift
首先看下工程组织结构

然后就是sb中的内容

接着就是源码了
1. AppDelegate.swift
import UIKit
//
// MARK: - App Delegate
//
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
//
// MARK: - Constants
//
let tintColor = UIColor(red: 242/255, green: 71/255, blue: 63/255, alpha: 1)
//
// MARK: - Variables And Properties
//
var backgroundSessionCompletionHandler: (() -> Void)?
var window: UIWindow?
//
// MARK: - Application Delegate
//
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
customizeAppearance()
return true
}
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession handleEventsForBackgroundURLSessionidentifier: String,
completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
//
// MARK - Private Methods
//
private func customizeAppearance() {
window?.tintColor = tintColor
UISearchBar.appearance().barTintColor = tintColor
UINavigationBar.appearance().barTintColor = tintColor
UINavigationBar.appearance().tintColor = UIColor.white
let titleTextAttributes = [NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : UIColor.white]
UINavigationBar.appearance().titleTextAttributes = titleTextAttributes
}
}
2. TrackCell.swift
import UIKit
//
// MARK: - Track Cell Delegate Protocol
//
protocol TrackCellDelegate {
func cancelTapped(_ cell: TrackCell)
func downloadTapped(_ cell: TrackCell)
func pauseTapped(_ cell: TrackCell)
func resumeTapped(_ cell: TrackCell)
}
//
// MARK: - Track Cell
//
class TrackCell: UITableViewCell {
//
// MARK: - Class Constants
//
static let identifier = "TrackCell"
//
// MARK: - IBOutlets
//
@IBOutlet weak var artistLabel: UILabel!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var downloadButton: UIButton!
@IBOutlet weak var pauseButton: UIButton!
@IBOutlet weak var progressLabel: UILabel!
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var titleLabel: UILabel!
//
// MARK: - Variables And Properties
//
/// Delegate identifies track for this cell, then
/// passes this to a download service method.
var delegate: TrackCellDelegate?
//
// MARK: - IBActions
//
@IBAction func cancelTapped(_ sender: AnyObject) {
delegate?.cancelTapped(self)
}
@IBAction func downloadTapped(_ sender: AnyObject) {
delegate?.downloadTapped(self)
}
@IBAction func pauseOrResumeTapped(_ sender: AnyObject) {
if(pauseButton.titleLabel?.text == "Pause") {
delegate?.pauseTapped(self)
} else {
delegate?.resumeTapped(self)
}
}
//
// MARK: - Internal Methods
//
func configure(track: Track, downloaded: Bool, download: Download?) {
titleLabel.text = track.name
artistLabel.text = track.artist
// Show/hide download controls Pause/Resume, Cancel buttons, progress info.
var showDownloadControls = false
// Non-nil Download object means a download is in progress.
if let download = download {
showDownloadControls = true
let title = download.isDownloading ? "Pause" : "Resume"
pauseButton.setTitle(title, for: .normal)
progressLabel.text = download.isDownloading ? "Downloading..." : "Paused"
}
pauseButton.isHidden = !showDownloadControls
cancelButton.isHidden = !showDownloadControls
progressView.isHidden = !showDownloadControls
progressLabel.isHidden = !showDownloadControls
// If the track is already downloaded, enable cell selection and hide the Download button.
selectionStyle = downloaded ? UITableViewCell.SelectionStyle.gray : UITableViewCell.SelectionStyle.none
downloadButton.isHidden = downloaded || showDownloadControls
}
func updateDisplay(progress: Float, totalSize : String) {
progressView.progress = progress
progressLabel.text = String(format: "%.1f%% of %@", progress * 100, totalSize)
}
}
3. Track.swift
import Foundation.NSURL
//
// MARK: - Track
//
/// Query service creates Track objects
class Track {
//
// MARK: - Constants
//
let artist: String
let index: Int
let name: String
let previewURL: URL
//
// MARK: - Variables And Properties
//
var downloaded = false
//
// MARK: - Initialization
//
init(name: String, artist: String, previewURL: URL, index: Int) {
self.name = name
self.artist = artist
self.previewURL = previewURL
self.index = index
}
}
4. Download.swift
import Foundation
//
// MARK: - Download
//
class Download {
//
// MARK: - Variables And Properties
//
var isDownloading = false
var progress: Float = 0
var resumeData: Data?
var task: URLSessionDownloadTask?
var track: Track
//
// MARK: - Initialization
//
init(track: Track) {
self.track = track
}
}
5. QueryService.swift
import Foundation
//
// MARK: - Query Service
//
/// Runs query data task, and stores results in array of Tracks
class QueryService {
//
// MARK: - Constants
//
let defaultSession = URLSession(configuration: .default)
//
// MARK: - Variables And Properties
//
var dataTask: URLSessionDataTask?
var errorMessage = ""
var tracks: [Track] = []
//
// MARK: - Type Alias
//
typealias JSONDictionary = [String: Any]
typealias QueryResult = ([Track]?, String) -> Void
//
// MARK: - Internal Methods
//
func getSearchResults(searchTerm: String, completion: @escaping QueryResult) {
// 1
dataTask?.cancel()
// 2
if var urlComponents = URLComponents(string: "https://itunes.apple.com/search") {
urlComponents.query = "media=music&entity=song&term=\(searchTerm)"
// 3
guard let url = urlComponents.url else {
return
}
// 4
dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
defer {
self?.dataTask = nil
}
// 5
if let error = error {
self?.errorMessage += "DataTask error: " + error.localizedDescription + "\n"
} else if
let data = data,
let response = response as? HTTPURLResponse,
response.statusCode == 200 {
self?.updateSearchResults(data)
// 6
DispatchQueue.main.async {
completion(self?.tracks, self?.errorMessage ?? "")
}
}
}
// 7
dataTask?.resume()
}
}
//
// MARK: - Private Methods
//
private func updateSearchResults(_ data: Data) {
var response: JSONDictionary?
tracks.removeAll()
do {
response = try JSONSerialization.jsonObject(with: data, options: []) as? JSONDictionary
} catch let parseError as NSError {
errorMessage += "JSONSerialization error: \(parseError.localizedDescription)\n"
return
}
guard let array = response!["results"] as? [Any] else {
errorMessage += "Dictionary does not contain results key\n"
return
}
var index = 0
for trackDictionary in array {
if let trackDictionary = trackDictionary as? JSONDictionary,
let previewURLString = trackDictionary["previewUrl"] as? String,
let previewURL = URL(string: previewURLString),
let name = trackDictionary["trackName"] as? String,
let artist = trackDictionary["artistName"] as? String {
tracks.append(Track(name: name, artist: artist, previewURL: previewURL, index: index))
index += 1
} else {
errorMessage += "Problem parsing trackDictionary\n"
}
}
}
}
6. DownloadService.swift
import Foundation
//
// MARK: - Download Service
//
/// Downloads song snippets, and stores in local file.
/// Allows cancel, pause, resume download.
class DownloadService {
//
// MARK: - Variables And Properties
//
var activeDownloads: [URL: Download] = [ : ]
/// SearchViewController creates downloadsSession
var downloadsSession: URLSession!
//
// MARK: - Internal Methods
//
func cancelDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else {
return
}
download.task?.cancel()
activeDownloads[track.previewURL] = nil
}
func pauseDownload(_ track: Track) {
guard
let download = activeDownloads[track.previewURL],
download.isDownloading
else {
return
}
download.task?.cancel(byProducingResumeData: { data in
download.resumeData = data
})
download.isDownloading = false
}
func resumeDownload(_ track: Track) {
guard let download = activeDownloads[track.previewURL] else {
return
}
if let resumeData = download.resumeData {
download.task = downloadsSession.downloadTask(withResumeData: resumeData)
} else {
download.task = downloadsSession.downloadTask(with: download.track.previewURL)
}
download.task?.resume()
download.isDownloading = true
}
func startDownload(_ track: Track) {
// 1
let download = Download(track: track)
// 2
download.task = downloadsSession.downloadTask(with: track.previewURL)
// 3
download.task?.resume()
// 4
download.isDownloading = true
// 5
activeDownloads[download.track.previewURL] = download
}
}
7. SearchViewController.swift
import AVFoundation
import AVKit
import UIKit
//
// MARK: - Search View Controller
//
class SearchViewController: UIViewController {
//
// MARK: - Constants
//
/// Get local file path: download task stores tune here; AV player plays it.
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let downloadService = DownloadService()
let queryService = QueryService()
//
// MARK: - IBOutlets
//
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
//
// MARK: - Variables And Properties
//
lazy var downloadsSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier:
"com.raywenderlich.HalfTunes.bgSession")
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
var searchResults: [Track] = []
lazy var tapRecognizer: UITapGestureRecognizer = {
var recognizer = UITapGestureRecognizer(target:self, action: #selector(dismissKeyboard))
return recognizer
}()
//
// MARK: - Internal Methods
//
@objc func dismissKeyboard() {
searchBar.resignFirstResponder()
}
func localFilePath(for url: URL) -> URL {
return documentsPath.appendingPathComponent(url.lastPathComponent)
}
func playDownload(_ track: Track) {
let playerViewController = AVPlayerViewController()
present(playerViewController, animated: true, completion: nil)
let url = localFilePath(for: track.previewURL)
let player = AVPlayer(url: url)
playerViewController.player = player
player.play()
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
func reload(_ row: Int) {
tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .none)
}
//
// MARK: - View Controller
//
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
downloadService.downloadsSession = downloadsSession
}
}
//
// MARK: - Search Bar Delegate
//
extension SearchViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
dismissKeyboard()
guard let searchText = searchBar.text, !searchText.isEmpty else {
return
}
UIApplication.shared.isNetworkActivityIndicatorVisible = true
queryService.getSearchResults(searchTerm: searchText) { [weak self] results, errorMessage in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
if let results = results {
self?.searchResults = results
self?.tableView.reloadData()
self?.tableView.setContentOffset(CGPoint.zero, animated: false)
}
if !errorMessage.isEmpty {
print("Search error: " + errorMessage)
}
}
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
view.addGestureRecognizer(tapRecognizer)
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
view.removeGestureRecognizer(tapRecognizer)
}
}
//
// MARK: - Table View Data Source
//
extension SearchViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TrackCell = tableView.dequeueReusableCell(withIdentifier: TrackCell.identifier,
for: indexPath) as! TrackCell
// Delegate cell button tap events to this view controller.
cell.delegate = self
let track = searchResults[indexPath.row]
cell.configure(track: track,
downloaded: track.downloaded,
download: downloadService.activeDownloads[track.previewURL])
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}
}
//
// MARK: - Table View Delegate
//
extension SearchViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//When user taps cell, play the local file, if it's downloaded.
let track = searchResults[indexPath.row]
if track.downloaded {
playDownload(track)
}
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 62.0
}
}
//
// MARK: - Track Cell Delegate
//
extension SearchViewController: TrackCellDelegate {
func cancelTapped(_ cell: TrackCell) {
if let indexPath = tableView.indexPath(for: cell) {
let track = searchResults[indexPath.row]
downloadService.cancelDownload(track)
reload(indexPath.row)
}
}
func downloadTapped(_ cell: TrackCell) {
if let indexPath = tableView.indexPath(for: cell) {
let track = searchResults[indexPath.row]
downloadService.startDownload(track)
reload(indexPath.row)
}
}
func pauseTapped(_ cell: TrackCell) {
if let indexPath = tableView.indexPath(for: cell) {
let track = searchResults[indexPath.row]
downloadService.pauseDownload(track)
reload(indexPath.row)
}
}
func resumeTapped(_ cell: TrackCell) {
if let indexPath = tableView.indexPath(for: cell) {
let track = searchResults[indexPath.row]
downloadService.resumeDownload(track)
reload(indexPath.row)
}
}
}
//
// MARK: - URL Session Delegate
//
extension SearchViewController: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
}
//
// MARK: - URL Session Download Delegate
//
extension SearchViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
// 1
guard let sourceURL = downloadTask.originalRequest?.url else {
return
}
let download = downloadService.activeDownloads[sourceURL]
downloadService.activeDownloads[sourceURL] = nil
// 2
let destinationURL = localFilePath(for: sourceURL)
print(destinationURL)
// 3
let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationURL)
do {
try fileManager.copyItem(at: location, to: destinationURL)
download?.track.downloaded = true
} catch let error {
print("Could not copy file to disk: \(error.localizedDescription)")
}
// 4
if let index = download?.track.index {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
}
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
// 1
guard
let url = downloadTask.originalRequest?.url,
let download = downloadService.activeDownloads[url] else {
return
}
// 2
download.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
// 3
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file)
// 4
DispatchQueue.main.async {
if let trackCell = self.tableView.cellForRow(at: IndexPath(row: download.track.index,
section: 0)) as? TrackCell {
trackCell.updateDisplay(progress: download.progress, totalSize: totalSize)
}
}
}
}
后记
本篇主要讲述了一种和网络请求有关的类
NSURLSession,感兴趣的给个赞或者关注~~~
