首頁 > 軟體

Swift HTTP載入請求Loading Requests教學

2023-11-01 18:01:04

正文

到目前為止,我們已經看了一個簡單的Request/Response的結構體和實現。接下來,我們來討論一下傳送請求和接收響應。

如果我們回想一下第一節,我們會用HTTP回撥給他,我們傳送了一個請求,並且最終得到了一個響應(忽略Error)沒有任何“任務”或者代理亦或其他什麼東西。我們傳送(或載入)一個請求,最終都會得到一個響應。

如果我們用一個方法來描述那個功能,那麼這個方法如下所示

func load(request: HTTPRequest, completion: @escaping (HTTPResponse) -> Void)

我們傳送了一個請求,在未來的某個節點,閉包將會被執行,表裡涵蓋的是我們要的響應。當然,單個方法並不是我們想要的;換句話說,我們想要表述的是一個介面。所以,我們會把他封裝進一個protocol中:

public protocol HTTPLoading {
    func load(request: HTTPRequest, completion: @escaping (HTTPResponse) -> Void)
}

當然,我們可能會得到一個error響應。所以,我們要用自己的“results”重新命名(typealiase)來替換HTTPResponse:

    func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void)

讓我們停下來欣賞一下。 這個方法就是我們定義網路框架的核心功能所需要的全部。 就是這樣:一個方法。 棒極了。

遵循HTTPLoading協定

URLSession就像“路上的橡膠”,這是在我們的請求通過空中(或有線)傳送到我們指定的伺服器之前需要清除的最後一個障礙。 因此,我們在 URLSession 上實現 HTTPLoading 是為了將 HTTPRequest 轉換為對談需要的 URLRequest,這是有道理的:

extension URLSession: HTTPLoading {
    public func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void)
        guard let url = request.url else {
            // we couldn't construct a proper URL out of the request's URLComponents
            completion(.failure(...))
            return
        }
        // construct the URLRequest
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = request.method.rawValue
        // copy over any custom HTTP headers
        for (header, value) in request.headers {
            urlRequest.addValue(value, forHTTPHeaderField: header)
        }
        if request.body.isEmpty == false {
            // if our body defines additional headers, add them
            for (header, value) in request.body.additionalHeaders {
                urlRequest.addValue(value, forHTTPHeaderField: header)
            }
            // attempt to retrieve the body data
            do {
                urlRequest.httpBody = try request.body.encode()
            } catch {
                // something went wrong creating the body; stop and report back
                completion(.failure(...))
                return
            }
        }
        let dataTask = session.dataTask(with: urlRequest) { (data, response, error) in
            // construct a Result<HTTPResponse, HTTPError> out of the triplet of data, url response, and url error
            let result = HTTPResult(request: request, responseData: data, response: response, error: error)
            completion(result)
        }
        // off we go!
        dataTask.resume()
    }
}

這應該很容易理解。 我們正在執行從 HTTPRequest 值中提取資訊並將其應用於 URLRequest 的步驟。 如果在任何時候出現問題,那麼我們將報告錯誤。 (您需要自己填寫 ... 部分以構造適當的 HTTPError 值)

假設構建一切順利,我們最終會得到一個 URLRequest,我們可以將其轉換為 URLSessionDataTask 並執行它。 當它完成時,我們將獲取響應值,將它們轉換為 HTTPResult,並通過完成塊報告回來。

建立Result

在傳輸過程中的任何時候,我們的請求都可能失敗。 如果我們處於飛航模式或其他“未連線”狀態,則請求可能永遠不會傳送。 如果我們正在傳送請求(在我們得到響應之前),網路連線可能會斷開。 或者它可能會在我們傳送後但在我們收到回覆之前掉線。 或者它可能會在我們開始收到響應之後但在完全接收到響應之前下降。

這就是我們在定義請求和響應型別時建立 HTTPError 結構的原因,這意味著我們需要更加努力地構建我們的結果,而不是簡單地檢查“我是否得到了一些資料”。

在高層次上,初始化 HTTPResult 的邏輯大致如下所示:

var httpResponse: HTTPResponse?
if let r = response as? HTTPURLResponse {
    httpResponse = HTTPResponse(request: request, response: r, body: responseData ?? Data())
}
if let e = error as? URLError {
    let code: HTTPError.Code
    switch e.code {
        case .badURL: code = .invalidRequest
        case .unsupportedURL: code = ...
        case .cannotFindHost: code = ...
        ...
        default: code = .unknown
    }
    self = .failure(HTTPError(code: code, request: request, response: httpResponse, underlyingError: e))
} else if let someError = error {
    // an error, but not a URL error
    self = .failure(HTTPError(code: .unknown, request: request, response: httpResponse, underlyingError: someError))
} else if let r = httpResponse {
    // not an error, and an HTTPURLResponse
    self = .success(r)
} else {
    // not an error, but also not an HTTPURLResponse
    self = .failure(HTTPError(code: .invalidResponse, request: request, response: nil, underlyingError: error))
}

用法

HTTPLoading 使用方法:

public class StarWarsAPI {
    private let loader: HTTPLoading = URLSession.shared
    public func requestPeople(completion: @escaping (...) -> Void) {
        var r = HTTPRequest()
        r.host = "swapi.dev"
        r.path = "/api/people"
        loader.load(request: r) { result in
            // TODO: interpret the result
            completion(...)
        }
    }
}

我想在這裡指出,我們在任何時候都不會解釋Response的狀態程式碼。 獲得 500 Internal Server Error404 Not Found 響應是成功的響應。 在這一層,“成功”意味著“我們得到了迴應”,而不是“迴應表明某種語意錯誤”。 解釋狀態程式碼是特定於應用程式的邏輯。 在未來的貼文中,我們將允許基於狀態程式碼的可客製化的、特定於應用程式的行為(例如跟隨重定向或重試請求)。

我們定義的這個單一方法看似簡單,但也不完整。 我們還沒有指出任何主動取消請求的方法,我們需要調整我們的 HTTPLoading 協定以新增更多功能。 我們還將把它從協定轉換為類,原因我將在以後的貼文中解釋。

儘管存在這些小漏洞,該協定在其簡單性方面仍然很漂亮,它展示了一個好的問題概念化如何能夠產生強大而美麗的東西。

簡單是最終的複雜。

在下一篇文章中,我們將研究使用 HTTPLoading 協定來簡化單元測試。

以上就是Swift HTTP載入請求Loading Requests教學的詳細內容,更多關於Swift HTTP載入請求的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com