iOS14開發-網絡

基礎知識

App如何經過網絡請求數據?

客戶服務器模型

  1. App 經過一個 URL 向特定的主機發送一個網絡請求加載須要的資源。URL 通常是使用 HTTP(HTTPS)協議,該協議會經過 IP(或域名)定位到資源所在的主機,而後等待主機處理和響應。
  2. 主機經過本次網絡請求指定的端口號找到對應的處理軟件,而後將網絡請求轉發給該軟件進行處理(處理的軟件會運行在特定的端口)。針對 HTTP(HTTPS)請求,處理的軟件會隨着開發語言的不一樣而不一樣,如 Java 的 Tomcat、PHP 的 Apache、.net 的 IIS、Node.js 的 JavaScript 運行時等)
  3. 處理軟件針對本次請求進行分析,分析的內容包括請求的方法、路徑以及攜帶的參數等。而後根據這些信息,進行相應的業務邏輯處理,最後經過主機將處理後的數據返回(返回的數據通常爲 JSON 字符串)。
  4. App 接收到主機返回的數據,進行解析處理,最後展現到界面上。
  5. 發送請求獲取資源的一方稱爲客戶端。接收請求提供服務的一方稱爲服務端

基本概念

URL

  • Uniform Resource Locator(統一資源定位符),表示網絡資源的地址或位置。
  • 互聯網上的每一個資源都有一個惟一的 URL,經過它能找到該資源。
  • URL 的基本格式協議://主機地址/路徑

HTTP/HTTPS

  • HTTP—HyperTextTransferProtocol:超文本傳輸協議。
  • HTTPS—Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext Transfer Protocol Secure:超文本傳輸安全協議。

請求方法

  • 在 HTTP/1.1 協議中,定義了 8 種發送 HTTP 請求的方法,分別是GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT
  • 最經常使用的是 GETPOST

響應狀態碼

狀態碼 描述 含義
200 Ok 請求成功
400 Bad Request 客戶端請求的語法出現錯誤,服務端沒法解析
404 Not Found 服務端沒法根據客戶端的請求找到對應的資源
500 Internal Server Error 服務端內部出現問題,沒法完成響應

請求響應過程

請求響應過程

JSON

  • JavaScript Object Notation。
  • 一種輕量級的數據格式,通常用於數據交互。
  • 服務端返回給 App 客戶端的數據,通常都是 JSON 格式。

語法

  • 數據以鍵值對key : value形式存在。
  • 多個數據由,分隔。
  • 花括號{}保存對象。
  • 方括號[]保存數組。

key與value

  • 標準 JSON 數據的 key 必須用雙引號""
  • JSON 數據的 value 類型:
    • 數字(整數或浮點數)
    • 字符串("表示)
    • 布爾值(true 或 false)
    • 數組([]表示)
    • 對象({}表示)
    • null

解析

  • 釐清當前 JSON 數據的層級關係(藉助於格式化工具)。
  • 明確每一個 key 對應的 value 值的類型。
  • 解析技術
    • Codable 協議(推薦)。
    • JSONSerialization。
    • 第三方框架。

URLSession

使用步驟

  1. 建立請求資源的 URL。
  2. 建立 URLRequest,設置請求參數。
  3. 建立 URLSessionConfiguration 用於設置 URLSession 的工做模式和網絡設置。
  4. 建立 URLSession。
  5. 經過 URLSession 構建 URLSessionTask,共有 3 種任務。

(1)URLSessionDataTask:請求數據的 Task。  (2)URLSessionUploadTask:上傳數據的 Task。 (3)URLSessionDownloadTask:下載數據的 Task。  6. 啓動任務。 7. 處理服務端響應,有 2 種方式。 (1)經過 completionHandler(閉包)處理服務端響應。 (2)經過 URLSessionDataDelegate(代理)處理請求與響應過程的事件和接收服務端返回的數據。javascript

基本使用

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // get()
        // post()
    }

    func get() {
        // 1. 肯定URL
        let url = URL(string: "http://v.juhe.cn/toutiao/index?type=top&key=申請的key")
        // 2. 建立請求
        let urlRequest = URLRequest(url: url!)
        // cachePolicy: 緩存策略,App最經常使用的緩存策略是returnCacheDataElseLoad,表示先查看緩存數據,沒有緩存再請求
        // timeoutInterval:超時時間
        // let urlRequest = URLRequest(url: url!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 5)
        let config = URLSessionConfiguration.default
        // 3. 建立URLSession
        let session = URLSession(configuration: config)
        // 4. 建立任務
        let task = session.dataTask(with: urlRequest) { data, _, error in
            if error != nil {
                print(error!)
            } else {
                if let data = data {
                    print(String(data: data, encoding: .utf8)!)
                }
            }
        }
        // 5. 啓動任務
        task.resume()
    }

    func post() {
        let url = URL(string: "http://v.juhe.cn/toutiao/index")
        var urlRequest = URLRequest(url: url!)
        // 指明請求方法
        urlRequest.httpMethod = "POST"
        // 指明參數
        let params = "type=top&key=申請的key"
        // 設置請求體
        urlRequest.httpBody = params.data(using: .utf8)
        let config = URLSessionConfiguration.default
        // delegateQueue決定了代理方法在哪一個線程中執行
        let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
        let task = session.dataTask(with: urlRequest)
        task.resume()
    }
}


// MARK:- URLSessionDataDelegate
extension ViewController: URLSessionDataDelegate {
    // 開始接收數據
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        // 容許接收服務器的數據,默認狀況下請求以後不接收服務器的數據即不會調用後面獲取數據的代理方法
        completionHandler(URLSession.ResponseDisposition.allow)
    }

    // 獲取數據
    // 根據請求的數據量該方法可能會調用屢次,這樣data返回的就是總數據的一段,此時須要用一個全局的Data進行追加存儲
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        let result = String(data: data, encoding: .utf8)
        if let result = result {
            print(result)
        }
    }

    // 獲取結束
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            print(error)
        } else {
            print("=======成功=======")
        }
    }
}
複製代碼

注意:若是網絡請求是 HTTP 而非 HTTPS,默認狀況下,iOS 會阻斷該請求,此時須要在 Info.plist 中進行以下配置。html

<key>NSAppTransportSecurity</key>
<dict>
	<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
複製代碼

URL轉碼與解碼

  • 當請求參數帶中文時,必須進行轉碼操做。
let url = "https://www.baidu.com?name=張三"
    .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
print(url) // URL中文轉碼
print(url.removingPercentEncoding!) // URL中文解碼
複製代碼
  • 有時候只須要對URL中的中文處理,而不須要針對整個URL。
let str = "阿楚姑娘"
    .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let url = URL(string: "https://music.163.com/#/search/m/?s=\(str)&type=1")
複製代碼

下載數據

class ViewController: UIViewController {
    // 下載進度
    @IBOutlet var downloadProgress: UIProgressView!
    // 下載圖片
    @IBOutlet var downloadImageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        download()
    }

    func download() {
        let url = URL(string: "http://172.20.53.240:8080/AppTestAPI/wall.png")!
        let request = URLRequest(url: url)
        let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue())
        let task = session.downloadTask(with: request)
        task.resume()
    }
}

extension ViewController: URLSessionDownloadDelegate {
    // 下載完成
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        // 存入沙盒
        let savePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
        // 文件類型根據下載的內容決定
        let fileName = "\(Int(Date().timeIntervalSince1970)).png"
        let filePath = savePath + "/" + fileName
        print(filePath)
        do {
            try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: filePath))
            // 顯示到界面
            DispatchQueue.main.async {
                self.downloadImageView.image = UIImage(contentsOfFile: filePath)
            }
        } catch {
            print(error)
        }
    }

    // 計算進度
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        DispatchQueue.main.async {
            self.downloadProgress.setProgress(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite), animated: true)
        }
    }
}
複製代碼

上傳數據

上傳數據須要服務端配合,不一樣的服務端代碼可能會不同,下面的上傳代碼適用於本人所寫的服務端代碼java

  • 數據格式。

上傳數據格式

  • 實現。
class ViewController: UIViewController {
    let YFBoundary = "AnHuiWuHuYungFan"
    @IBOutlet var uploadInfo: UILabel!
    @IBOutlet var uploadProgress: UIProgressView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        upload()
    }

    func upload() {
        // 1. 肯定URL
        let url = URL(string: "http://172.20.53.240:8080/AppTestAPI/UploadServlet")!
        // 2. 肯定請求
        var request = URLRequest(url: url)
        // 3. 設置請求頭
        let head = "multipart/form-data;boundary=\(YFBoundary)"
        request.setValue(head, forHTTPHeaderField: "Content-Type")
        // 4. 設置請求方式
        request.httpMethod = "POST"
        // 5. 建立NSURLSession
        let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue())
        // 6. 獲取上傳的數據(按照固定格式拼接)
        var data = Data()
        let header = headerString(mimeType: "image/png", uploadFile: "wall.png")
        data.append(header.data(using: .utf8)!)
        data.append(uploadData())
        let tailer = tailerString()
        data.append(tailer.data(using: .utf8)!)
        // 7. 建立上傳任務 上傳的數據來自getData方法
        let task = session.uploadTask(with: request, from: data) { _, _, error in
            // 上傳完畢後
            if error != nil {
                print(error!)
            } else {
                DispatchQueue.main.async {
                    self.uploadInfo.text = "上傳成功"
                }
            }
        }
        // 8. 執行上傳任務
        task.resume()
    }

    // 開始標記
    func headerString(mimeType: String, uploadFile: String) -> String {
        var data = String()
        // --Boundary\r\n
        data.append("--" + YFBoundary + "\r\n")
        // 文件參數名 Content-Disposition: form-data; name="myfile"; filename="wall.jpg"\r\n
        data.append("Content-Disposition:form-data; name=\"myfile\";filename=\"\(uploadFile)\"\r\n")
        // Content-Type 上傳文件的類型 MIME\r\n\r\n
        data.append("Content-Type:\(mimeType)\r\n\r\n")

        return data
    }

    // 結束標記
    func tailerString() -> String {
        // \r\n--Boundary--\r\n
        return "\r\n--" + YFBoundary + "--\r\n"
    }

    func uploadData() -> Data {
        let image = UIImage(named: "wall.png")
        let imageData = image!.pngData()
        return imageData!
    }
}

extension ViewController: URLSessionTaskDelegate {
    // 上傳進去
    func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
        DispatchQueue.main.async {
            self.uploadProgress.setProgress(Float(totalBytesSent) / Float(totalBytesExpectedToSend), animated: true)
        }
    }
    
    // 上傳出錯
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            print(error)
        }
    }
}
複製代碼

URLCache

  • 網絡緩存有不少好處:節省流量、更快加載、斷網可用。
  • 使用 URLCache 管理緩存區域的大小和數據。
  • 每個 App 都默認建立了一個 URLCache 做爲緩存管理者,能夠經過URLCache.shared獲取,也能夠自定義。
// 建立URLCache
// memoryCapacity:內存緩存容量
// diskCapacity:硬盤緩存容量
// directory:硬盤緩存路徑
let cache = URLCache(memoryCapacity: 10 * 1024 * 1024, diskCapacity: 100 * 1024 * 1024, directory: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first)
// 替換默認的緩存管理對象
URLCache.shared = cache
複製代碼
  • 常見屬性與方法。
let url = URL(string: "http://v.juhe.cn/toutiao/index?type=top&key=申請的key")
let urlRequest = URLRequest(url: url!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 5)
let cache = URLCache.shared

// 內存緩存大小
cache.memoryCapacity
// 硬盤緩存大小
cache.diskCapacity
// 已用內存緩存大小
cache.currentMemoryUsage
// 已用硬盤緩存大小
cache.currentDiskUsage
// 獲取某個請求的緩存
let cacheResponse = cache.cachedResponse(for: urlRequest)
// 刪除某個請求的緩存
cache.removeCachedResponse(for: urlRequest)
// 刪除某個時間點開始的緩存
cache.removeCachedResponses(since: Date().addingTimeInterval(-60 * 60 * 48))
// 刪除全部緩存
cache.removeAllCachedResponses()
複製代碼

WKWebView

  • 用於加載 Web 內容的控件。
  • 使用時必須導入WebKit模塊。

基本使用

  • 加載網頁。
// 建立URL
let url = URL(string: "https://www.abc.edu.cn")
// 建立URLRequest
let request = URLRequest(url: url!)
// 建立WKWebView
let webView = WKWebView(frame: UIScreen.main.bounds)
// 加載網頁
webView.load(request)
複製代碼
  • 加載本地資源。
// 文件夾路徑
let basePath = Bundle.main.path(forResource: "localWeb", ofType: nil)!
// 文件夾URL
let baseUrl = URL(fileURLWithPath: basePath, isDirectory: true)
// html路徑
let filePath = basePath + "/index.html"
// 轉成文件
let fileContent = try? NSString(contentsOfFile: filePath, encoding: String.Encoding.utf8.rawValue)
// 建立WKWebView
let webView = WKWebView(frame: UIScreen.main.bounds)
// 加載html
webView.loadHTMLString(fileContent! as String, baseURL: baseUrl)
複製代碼

注意:若是是本地資源是文件夾,拖進項目時,須要勾選Create folder references,而後用Bundle.main.path(forResource: "文件夾名", ofType: nil)獲取資源路徑。web

與JavaScript交互

建立WKWebView

lazy var webView: WKWebView = {
    // 建立WKPreferences
    let preferences = WKPreferences()
    // 開啓JavaScript
    preferences.javaScriptEnabled = true
    // 建立WKWebViewConfiguration
    let configuration = WKWebViewConfiguration()
    // 設置WKWebViewConfiguration的WKPreferences
    configuration.preferences = preferences
    // 建立WKUserContentController
    let userContentController = WKUserContentController()
    // 配置WKWebViewConfiguration的WKUserContentController
    configuration.userContentController = userContentController
    // 給WKWebView與Swift交互起一個名字:callbackHandler,WKWebView給Swift發消息的時候會用到
    // 此句要求實現WKScriptMessageHandler
    configuration.userContentController.add(self, name: "callbackHandler")    
    // 建立WKWebView
    var webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
    // 讓WKWebView翻動有回彈效果
    webView.scrollView.bounces = true
    // 只容許WKWebView上下滾動
    webView.scrollView.alwaysBounceVertical = true
    // 設置代理WKNavigationDelegate
    webView.navigationDelegate = self
    // 返回
    return webView
}()
複製代碼

建立HTML

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,user-scalable=no"/>
    </head>
    <body>
        iOS傳過來的值:<span id="name"></span>
        <button onclick="responseSwift()">響應iOS</button>
        <script type="text/javascript"> // 給Swift調用 function sayHello(name) { document.getElementById("name").innerHTML = name return "Swift你也好!" } // 調用Swift方法 function responseSwift() { // 這裏的callbackHandler是建立WKWebViewConfiguration是定義的 window.webkit.messageHandlers.callbackHandler.postMessage("JavaScript發送消息給Swift") } </script>
    </body>
</html>
複製代碼

兩個協議

  • WKNavigationDelegate:判斷頁面加載完成,只有在頁面加載完成後才能在實現 Swift 調用 JavaScript。WKWebView 調用 JavaScript:
// 加載完畢之後執行
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    // 調用JavaScript方法
    webView.evaluateJavaScript("sayHello('WebView你好!')") { (result, err) in
        // result是JavaScript返回的值
        print(result, err)
    }
}
複製代碼
  • WKScriptMessageHandler:JavaScript 調用 Swift 時須要用到協議中的一個方法來。JavaScript 調用 WKWebView:
// Swift方法,能夠在JavaScript中調用
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    print(message.body)
}
複製代碼

ViewController

class ViewController: UIViewController {
    // 懶加載WKWebView
    ...

    // 加載本地html
    let html = try! String(contentsOfFile: Bundle.main.path(forResource: "index", ofType: "html")!, encoding: String.Encoding.utf8)

    override func viewDidLoad() {
        super.viewDidLoad()
        // 標題
        title = "WebView與JavaScript交互"
        // 加載html
        webView.loadHTMLString(html, baseURL: nil)
        view.addSubview(webView)
    }
}

// 遵照兩個協議
extension ViewController: WKNavigationDelegate, WKScriptMessageHandler {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        ...
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        ...
    }
}
複製代碼

SFSafariViewController

  • iOS 9 推出的一種 UIViewController,用於加載與顯示 Web 內容,打開效果相似 Safari 瀏覽器的效果。
  • 使用時必須導入SafariServices模塊。
import SafariServices

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        showSafariViewController()
    }

    func showSafariViewController() {
        // URL
        let url = URL(string: "https://www.baidu.com")
        // 建立SFSafariViewController
        let sf = SFSafariViewController(url: url!)
        // 設置代理
        sf.delegate = self
        // 顯示
        present(sf, animated: true, completion: nil)
    }
}

extension ViewController: SFSafariViewControllerDelegate {
    // 點擊左上角的完成(done)
    func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
        print(#function)
    }

    // 加載完成
    func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
        print(#function)
    }
}
複製代碼
相關文章
相關標籤/搜索