基礎(chǔ)知識(shí)
App如何通過(guò)網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)?
客戶服務(wù)器模型
- App 通過(guò)一個(gè) URL 向特定的主機(jī)發(fā)送一個(gè)網(wǎng)絡(luò)請(qǐng)求加載需要的資源。URL 一般是使用 HTTP(HTTPS)協(xié)議题翻,該協(xié)議會(huì)通過(guò) IP(或域名)定位到資源所在的主機(jī)浙宜,然后等待主機(jī)處理和響應(yīng)。
- 主機(jī)通過(guò)本次網(wǎng)絡(luò)請(qǐng)求指定的端口號(hào)找到對(duì)應(yīng)的處理軟件汛聚,然后將網(wǎng)絡(luò)請(qǐng)求轉(zhuǎn)發(fā)給該軟件進(jìn)行處理(處理的軟件會(huì)運(yùn)行在特定的端口)。針對(duì) HTTP(HTTPS)請(qǐng)求短荐,處理的軟件會(huì)隨著開發(fā)語(yǔ)言的不同而不同倚舀,如 Java 的 Tomcat、PHP 的 Apache忍宋、.net 的 IIS痕貌、Node.js 的 JavaScript 運(yùn)行時(shí)等)
- 處理軟件針對(duì)本次請(qǐng)求進(jìn)行分析,分析的內(nèi)容包括請(qǐng)求的方法糠排、路徑以及攜帶的參數(shù)等舵稠。然后根據(jù)這些信息,進(jìn)行相應(yīng)的業(yè)務(wù)邏輯處理乳讥,最后通過(guò)主機(jī)將處理后的數(shù)據(jù)返回(返回的數(shù)據(jù)一般為 JSON 字符串)柱查。
- App 接收到主機(jī)返回的數(shù)據(jù),進(jìn)行解析處理云石,最后展示到界面上唉工。
- 發(fā)送請(qǐng)求獲取資源的一方稱為客戶端。接收請(qǐng)求提供服務(wù)的一方稱為服務(wù)端汹忠。
基本概念
URL
- Uniform Resource Locator(統(tǒng)一資源定位符)淋硝,表示網(wǎng)絡(luò)資源的地址或位置。
- 互聯(lián)網(wǎng)上的每個(gè)資源都有一個(gè)唯一的 URL宽菜,通過(guò)它能找到該資源谣膳。
- URL 的基本格式
協(xié)議://主機(jī)地址/路徑
。
HTTP/HTTPS
- HTTP—HyperTextTransferProtocol:超文本傳輸協(xié)議铅乡。
- HTTPS—Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext Transfer Protocol Secure:超文本傳輸安全協(xié)議继谚。
請(qǐng)求方法
- 在 HTTP/1.1 協(xié)議中,定義了 8 種發(fā)送 HTTP 請(qǐng)求的方法阵幸,分別是
GET花履、POST、HEAD挚赊、PUT诡壁、DELETE、OPTIONS荠割、TRACE妹卿、CONNECT
。 - 最常用的是 GET 與 POST。
響應(yīng)狀態(tài)碼
狀態(tài)碼 | 描述 | 含義 |
---|---|---|
200 | Ok | 請(qǐng)求成功 |
400 | Bad Request | 客戶端請(qǐng)求的語(yǔ)法出現(xiàn)錯(cuò)誤夺克,服務(wù)端無(wú)法解析 |
404 | Not Found | 服務(wù)端無(wú)法根據(jù)客戶端的請(qǐng)求找到對(duì)應(yīng)的資源 |
500 | Internal Server Error | 服務(wù)端內(nèi)部出現(xiàn)問(wèn)題箕宙,無(wú)法完成響應(yīng) |
請(qǐng)求響應(yīng)過(guò)程
請(qǐng)求響應(yīng)過(guò)程
JSON
- JavaScript Object Notation。
- 一種輕量級(jí)的數(shù)據(jù)格式铺纽,一般用于數(shù)據(jù)交互扒吁。
- 服務(wù)端返回給 App 客戶端的數(shù)據(jù),一般都是 JSON 格式室囊。
語(yǔ)法
- 數(shù)據(jù)以鍵值對(duì)
key : value
形式存在。 - 多個(gè)數(shù)據(jù)由
,
分隔魁索。 - 花括號(hào)
{}
保存對(duì)象融撞。 - 方括號(hào)
[]
保存數(shù)組。
key與value
- 標(biāo)準(zhǔn) JSON 數(shù)據(jù)的 key 必須用雙引號(hào)
""
粗蔚。 - JSON 數(shù)據(jù)的 value 類型:
- 數(shù)字(整數(shù)或浮點(diǎn)數(shù))
- 字符串(
"
表示) - 布爾值(true 或 false)
- 數(shù)組(
[]
表示) - 對(duì)象(
{}
表示) - null
解析
- 厘清當(dāng)前 JSON 數(shù)據(jù)的層級(jí)關(guān)系(借助于格式化工具)尝偎。
- 明確每個(gè) key 對(duì)應(yīng)的 value 值的類型。
- 解析技術(shù)
- Codable 協(xié)議(推薦)鹏控。
- JSONSerialization致扯。
- 第三方框架。
URLSession
使用步驟
- 創(chuàng)建請(qǐng)求資源的 URL当辐。
- 創(chuàng)建 URLRequest抖僵,設(shè)置請(qǐng)求參數(shù)。
- 創(chuàng)建 URLSessionConfiguration 用于設(shè)置 URLSession 的工作模式和網(wǎng)絡(luò)設(shè)置缘揪。
- 創(chuàng)建 URLSession耍群。
- 通過(guò) URLSession 構(gòu)建 URLSessionTask,共有 3 種任務(wù)找筝。
(1)URLSessionDataTask:請(qǐng)求數(shù)據(jù)的 Task蹈垢。
(2)URLSessionUploadTask:上傳數(shù)據(jù)的 Task。
(3)URLSessionDownloadTask:下載數(shù)據(jù)的 Task袖裕。 - 啟動(dòng)任務(wù)曹抬。
- 處理服務(wù)端響應(yīng),有 2 種方式急鳄。
(1)通過(guò) completionHandler(閉包)處理服務(wù)端響應(yīng)谤民。
(2)通過(guò) URLSessionDataDelegate(代理)處理請(qǐng)求與響應(yīng)過(guò)程的事件和接收服務(wù)端返回的數(shù)據(jù)。
基本使用
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=申請(qǐng)的key")
// 2. 創(chuàng)建請(qǐng)求
let urlRequest = URLRequest(url: url!)
// cachePolicy: 緩存策略攒岛,App最常用的緩存策略是returnCacheDataElseLoad赖临,表示先查看緩存數(shù)據(jù),沒有緩存再請(qǐng)求
// timeoutInterval:超時(shí)時(shí)間
// let urlRequest = URLRequest(url: url!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 5)
let config = URLSessionConfiguration.default
// 3. 創(chuàng)建URLSession
let session = URLSession(configuration: config)
// 4. 創(chuàng)建任務(wù)
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. 啟動(dòng)任務(wù)
task.resume()
}
func post() {
let url = URL(string: "http://v.juhe.cn/toutiao/index")
var urlRequest = URLRequest(url: url!)
// 指明請(qǐng)求方法
urlRequest.httpMethod = "POST"
// 指明參數(shù)
let params = "type=top&申請(qǐng)的key"
// 設(shè)置請(qǐng)求體
urlRequest.httpBody = params.data(using: .utf8)
let config = URLSessionConfiguration.default
// delegateQueue決定了代理方法在哪個(gè)線程中執(zhí)行
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
let task = session.dataTask(with: urlRequest)
task.resume()
}
}
// MARK:- URLSessionDataDelegate
extension ViewController: URLSessionDataDelegate {
// 開始接收數(shù)據(jù)
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
// 允許接收服務(wù)器的數(shù)據(jù)灾锯,默認(rèn)情況下請(qǐng)求之后不接收服務(wù)器的數(shù)據(jù)即不會(huì)調(diào)用后面獲取數(shù)據(jù)的代理方法
completionHandler(URLSession.ResponseDisposition.allow)
}
// 獲取數(shù)據(jù)
// 根據(jù)請(qǐng)求的數(shù)據(jù)量該方法可能會(huì)調(diào)用多次兢榨,這樣data返回的就是總數(shù)據(jù)的一段,此時(shí)需要用一個(gè)全局的Data進(jìn)行追加存儲(chǔ)
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
let result = String(data: data, encoding: .utf8)
if let result = result {
print(result)
}
}
// 獲取結(jié)束
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error)
} else {
print("=======成功=======")
}
}
}
注意:如果網(wǎng)絡(luò)請(qǐng)求是 HTTP 而非 HTTPS,默認(rèn)情況下吵聪,iOS 會(huì)阻斷該請(qǐng)求凌那,此時(shí)需要在 Info.plist 中進(jìn)行如下配置。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
下載數(shù)據(jù)
class ViewController: UIViewController {
// 下載進(jìn)度
@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!
// 文件類型根據(jù)下載的內(nèi)容決定
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)
}
}
// 計(jì)算進(jìn)度
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)
}
}
}
上傳數(shù)據(jù)
上傳數(shù)據(jù)需要服務(wù)端配合吟逝,不同的服務(wù)端代碼可能會(huì)不一樣帽蝶,下面的上傳代碼適用于本人所寫的服務(wù)端代碼。
- 數(shù)據(jù)格式块攒。
上傳數(shù)據(jù)格式
- 實(shí)現(xiàn)励稳。
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. 確定請(qǐng)求
var request = URLRequest(url: url)
// 3. 設(shè)置請(qǐng)求頭
let head = "multipart/form-data;boundary=\(YFBoundary)"
request.setValue(head, forHTTPHeaderField: "Content-Type")
// 4. 設(shè)置請(qǐng)求方式
request.httpMethod = "POST"
// 5. 創(chuàng)建NSURLSession
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue())
// 6. 獲取上傳的數(shù)據(jù)(按照固定格式拼接)
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. 創(chuàng)建上傳任務(wù) 上傳的數(shù)據(jù)來(lái)自getData方法
let task = session.uploadTask(with: request, from: data) { _, _, error in
// 上傳完畢后
if error != nil {
print(error!)
} else {
DispatchQueue.main.async {
self.uploadInfo.text = "上傳成功"
}
}
}
// 8. 執(zhí)行上傳任務(wù)
task.resume()
}
// 開始標(biāo)記
func headerString(mimeType: String, uploadFile: String) -> String {
var data = String()
// --Boundary\r\n
data.append("--" + YFBoundary + "\r\n")
// 文件參數(shù)名 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
}
// 結(jié)束標(biāo)記
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 {
// 上傳進(jìn)去
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)
}
}
// 上傳出錯(cuò)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error)
}
}
}
WKWebView
- 用于加載 Web 內(nèi)容的控件。
- 使用時(shí)必須導(dǎo)入
WebKit
模塊囱井。
基本使用
- 加載網(wǎng)頁(yè)驹尼。
// 創(chuàng)建URL
let url = URL(string: "https://www.abc.edu.cn")
// 創(chuàng)建URLRequest
let request = URLRequest(url: url!)
// 創(chuàng)建WKWebView
let webView = WKWebView(frame: UIScreen.main.bounds)
// 加載網(wǎng)頁(yè)
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"
// 轉(zhuǎn)成文件
let fileContent = try? NSString(contentsOfFile: filePath, encoding: String.Encoding.utf8.rawValue)
// 創(chuàng)建WKWebView
let webView = WKWebView(frame: UIScreen.main.bounds)
// 加載html
webView.loadHTMLString(fileContent! as String, baseURL: baseUrl)
注意:如果是本地資源是文件夾庞呕,拖進(jìn)項(xiàng)目時(shí)新翎,需要勾選
Create folder references
,然后用Bundle.main.path(forResource: "文件夾名", ofType: nil)
獲取資源路徑住练。
與JavaScript交互
創(chuàng)建WKWebView
lazy var webView: WKWebView = {
// 創(chuàng)建WKPreferences
let preferences = WKPreferences()
// 開啟JavaScript
preferences.javaScriptEnabled = true
// 創(chuàng)建WKWebViewConfiguration
let configuration = WKWebViewConfiguration()
// 設(shè)置WKWebViewConfiguration的WKPreferences
configuration.preferences = preferences
// 創(chuàng)建WKUserContentController
let userContentController = WKUserContentController()
// 配置WKWebViewConfiguration的WKUserContentController
configuration.userContentController = userContentController
// 給WKWebView與Swift交互起一個(gè)名字:callbackHandler地啰,WKWebView給Swift發(fā)消息的時(shí)候會(huì)用到
// 此句要求實(shí)現(xiàn)WKScriptMessageHandler
configuration.userContentController.add(self, name: "callbackHandler")
// 創(chuàng)建WKWebView
var webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
// 讓W(xué)KWebView翻動(dòng)有回彈效果
webView.scrollView.bounces = true
// 只允許WKWebView上下滾動(dòng)
webView.scrollView.alwaysBounceVertical = true
// 設(shè)置代理WKNavigationDelegate
webView.navigationDelegate = self
// 返回
return webView
}()
創(chuàng)建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傳過(guò)來(lái)的值:<span id="name"></span>
<button onclick="responseSwift()">響應(yīng)iOS</button>
<script type="text/javascript">
// 給Swift調(diào)用
function sayHello(name) {
document.getElementById("name").innerHTML = name
return "Swift你也好!"
}
// 調(diào)用Swift方法
function responseSwift() {
// 這里的callbackHandler是創(chuàng)建WKWebViewConfiguration是定義的
window.webkit.messageHandlers.callbackHandler.postMessage("JavaScript發(fā)送消息給Swift")
}
</script>
</body>
</html>
兩個(gè)協(xié)議
- WKNavigationDelegate:判斷頁(yè)面加載完成讲逛,只有在頁(yè)面加載完成后才能在實(shí)現(xiàn) Swift 調(diào)用 JavaScript亏吝。WKWebView 調(diào)用 JavaScript:
// 加載完畢以后執(zhí)行
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// 調(diào)用JavaScript方法
webView.evaluateJavaScript("sayHello('WebView你好!')") { (result, err) in
// result是JavaScript返回的值
print(result, err)
}
}
- WKScriptMessageHandler:JavaScript 調(diào)用 Swift 時(shí)需要用到協(xié)議中的一個(gè)方法來(lái)盏混。JavaScript 調(diào)用 WKWebView:
// Swift方法顺呕,可以在JavaScript中調(diào)用
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()
// 標(biāo)題
title = "WebView與JavaScript交互"
// 加載html
webView.loadHTMLString(html, baseURL: nil)
view.addSubview(webView)
}
}
// 遵守兩個(gè)協(xié)議
extension ViewController: WKNavigationDelegate, WKScriptMessageHandler {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
...
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
...
}
}
SFSafariViewController
- iOS 9 推出的一種 UIViewController,用于加載與顯示 Web 內(nèi)容括饶,打開效果類似 Safari 瀏覽器的效果株茶。
- 使用時(shí)必須導(dǎo)入
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")
// 創(chuàng)建SFSafariViewController
let sf = SFSafariViewController(url: url!)
// 設(shè)置代理
sf.delegate = self
// 顯示
present(sf, animated: true, completion: nil)
}
}
extension ViewController: SFSafariViewControllerDelegate {
// 點(diǎn)擊左上角的完成(done)
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
print(#function)
}
// 加載完成
func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
print(#function)
}
}