關於 URLSession - part1

以前開發的時候,通常用第三方元件像是Alamofire或是AFNetworking用慣了,一兩行就直接可以Call api做get/post的動作,但其實他們的底層還是用NSURLSession的東西。 

最近看了原始的文件,順便筆記一下。

  • URLSessionDataTask - 下載資料到memory. 
  • URLSessionDownloadTask - 下載資料到檔案系統,如果要在背景下載則需使用URLSessionDownloadTask
  • URLSessionConfiguaration - 在上傳或下載中的相關設定放在這,在使用URLSession前就應該先把URLSessionConfiguration先設定好。

詳細的資料可以參考Apple document - URLSession

直接寫個簡單的Code會比較有概念,Let's go!

我會先介紹不使用delegation methods下如何簡單使用URLSessionTask下載,然後再修改一下加入delegation methods並使用progress bar,最後使用URLSessionDownloadTask 來完成background 下載。


首先我們先新增一個Single view專案。在專案首頁(也才一個Viewcontroller) 加入一個Imageview 和 一個 button



接著連結IBOutlet到這兩個物件上,並且加入button的action。

我們要做的效果是當按下Load Image按鈕後從網址載入圖片。
@IBAction func action_loadImage(_ sender: UIButton) {
//disable button in case user click twice
sender.isEnabled = false
//start loading image
startLoad()
}
func startLoad() {
let url = URL(string: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
//if has error then return
if let error = error {
self.handleClientError(error)
}
//if status code not in 200~299 , then something happen on server side
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
//load image data
if let mimeType = httpResponse.mimeType, mimeType == "image/png", let data = data{
DispatchQueue.main.async {
self.imageview1.image = UIImage(data: data)
self.button1.isEnabled = true
}
}
}
task.resume()
}
//handle client side error here
func handleClientError(_ error: Error) {
print("client side error")
}
//handle server side error here
func handleServerError(_ response: URLResponse?) {
print("server side error")
}
可以看到當使用者按下按鈕後,先disable按鈕,然後去觸發startLoad這個方法開始下載圖片。

很簡單吧!

進階部分(optional)
我們可以把URLSession下載的邏輯這部分抽出來,方便之後可以重複使用。

新增一個HTTPNetworking.swift 檔案(File -> New File -> Swift File)


打開HTTPNetworking.swift 將下面的程式碼複製上去
//
// HTTPNetworking.swift
// urlSessionDemo
//
// Created by Nick on 12/2/18.
// Copyright © 2018 NickOwn. All rights reserved.
//
import Foundation
import UIKit
protocol Networking {
typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
func request(from currentVC:UIViewController, url:URL, completion:@escaping(_ data:Data?) ->() )
}
struct HTTPNetworking: Networking {
func request(from currentVC:UIViewController,url: URL, completion:@escaping (_ data:Data?) -> ()) {
let task = createDataTask(from: currentVC, url: url, completion: { data,response, error in
//if error, call handleError method
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
self.handleError(currentVC, response)
return
}
completion(data)
})
task.resume()
}
//handle server side error with alert view controller
func handleError(_ vc:UIViewController,_ response: URLResponse?) {
let alertVC = UIAlertController(title: "Error", message: "Something wrong", preferredStyle: .alert)
let action_OK = UIAlertAction(title: "Got it", style: .cancel, handler: nil)
alertVC.addAction(action_OK)
vc.present(alertVC, animated: true, completion: nil)
}
private func createDataTask(from currentVC:UIViewController, url:URL, completion:@escaping CompletionHandler) -> URLSessionTask{
return URLSession.shared.dataTask(with: url){ data, response, error in
completion(data, response, error)
}
}
}
view raw network.swift hosted with ❤ by GitHub
我把completionhandle直接typealias一個新的type(本質上不變),然後公開了一個request方法。
當程式呼叫request這個方法時,會去產生URLSessionTask並且開始下載,如果有錯誤則是用AlertView顯示出來。


好了,回到原來的startLoad,我們要稍微修改一下。
// func startLoad() {
// let url = URL(string: "https://lemulotdotorg.files.wordpress.com/2016/10/dsc00737.jpg")!
// //init data
// receivedData = Data()
// let task = session.dataTask(with: url)
// task.resume()
//
// }
func startLoad(){
let url = URL(string: "https://lemulotdotorg.files.wordpress.com/2016/10/dsc00737.jpg")!
let networking = HTTPNetworking()
networking.request(from: self, url: url, completion: { data in
if let data = data {
DispatchQueue.main.async {
self.button1.isEnabled = true
self.imageview1.image = UIImage(data: data)
}
}
})
}
view raw startload.swift hosted with ❤ by GitHub

這樣如果之後有需要用到URLSession的時候,我們直接呼叫Networking就可以了,不用重複寫一樣的代碼,也方便維護。


下一章我們加入progress bar的應用。

專案的Source code在此

留言