![](/img/trans.png)
[英]Storing data from an asynchronous closure during URLSession.shared.dataTask
[英]URLSession.shared.dataTask correct way to receive data
美好的一天!
我在嘗試尋找正確的順序來檢查從dataTask收到的(數據,響應,錯誤)並進行一些特殊的錯誤處理時有些困惑。
通常我們的URLSession看起來像這樣:
class HTTPRequest {
static func request(urlStr: String, parameters: [String: String], completion: @escaping (_ data: Data?,_ response: URLResponse?, _ error: Error?) -> ()) {
var url = OpenExchange.base_URL + urlStr
url += getParameters(param: parameters)
let request = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print("URLSession Error: \(String(describing: error?.localizedDescription))")
completion(nil,nil,error)
} else {
completion(data,response,nil)
}
}
task.resume()
}
static func getParameters(param: [String: String]) -> String {
var data = [String]()
for (key,value) in param {
data.append(key + "=\(value)")
}
return data.map { String($0) }.joined(separator: "&")
}
}
我還有另一個內部帶有HTTPRequest的函數,用於將所有內容包裝到我正在使用的對象類型中:
static func networkOperation(urlStr: String, parameters: [String: String], completion: @escaping (ReturnedData) -> () ) {
var recieved = ReturnedData()
HTTPRequest.request(urlStr: urlStr, parameters: parameters) { (data, resp, err) in
if let data = data, let response = resp {
// TODO: try JSONDecoder() if data is API Error Struct; Moderate this section depending on results of decoding;
recieved.data = data
recieved.response = response
recieved.result = .Success
completion(recieved)
return
} else if err == nil {
recieved.result = .ErrorUnknown
completion(recieved)
return
}
recieved.error = err as NSError?
completion(recieved)
}
}
public struct ReturnedData {
public var data: Data?
public var response: URLResponse?
public var error: Error?
public var result: RequestResult = .ErrorHTTP
}
public enum RequestResult: String {
case Success
case ErrorAPI
case ErrorHTTP
case ErrorUnknown
}
使用上面的代碼,我可以輕松地創建不同的networkOperation調用,以執行不同的API方法並處理返回的不同數據模型。 我正在嘗試實現的是API錯誤檢查。 由於我的API有一些錯誤描述,例如當您輸入錯誤的APP_ID或當前的APP_ID無權獲取信息等時。因此,如果發生任何此類情況,數據將如下所示:
{
"error": true,
"status": 401,
"message": "invalid_app_id",
"description": "Invalid App ID provided - please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org."
}
我認為嘗試用networkOperations“ // TODO”標記中的Error結構解碼每個接收到的數據是不正確的,也許有一些好的方法可以實現這一點?
您應該讓API錯誤返回錯誤對象。
例如,您可以這樣做:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
}
在哪里將響應編碼到稱為ApiResultCode
的enum
ApiResultCode
所示:
enum ApiResultCode {
case invalidAppId
case recordNotFound // just an example
...
case unknown(String)
}
extension ApiResultCode {
static func code(for string: String) -> ApiResultCode {
switch string {
case "invalid_app_id": return .invalidAppId
case "record_not_found": return .recordNotFound
...
default: return .unknown(string)
}
}
}
該枚舉使您可以檢查message
代碼,而不會在字符串文字中亂扔代碼。
而且,如果您解析API錯誤,則可以返回該錯誤。 例如
if responseObject.error {
let error = NetworkRequestError.api(responseObject.status, ApiResultCode.code(for: responseObject.message), responseObject.description)
... now pass this `error`, just like any other `Error` object
}
如果您願意進行更廣泛的重新設計,我個人建議
RequestResult
以提取那些單獨的錯誤類型(調用方只想知道它是成功還是失敗...如果失敗,則應查看Error
對象以確定失敗的原因); Result
枚舉包含關聯的值,即成功Data
和失敗Error
; 和 ReturnedData
。 因此,首先,讓我們擴展RequestResult
使其包含失敗時的錯誤和成功時的有效負載:
public enum Result {
case success(Data)
case failure(Error)
}
實際上,現代慣例是使此通用,上面的代碼使用以下內容變為Result<Data, Error>
:
public enum Result<T, U> {
case success(T)
case failure(U)
}
(Swift 5實際上包含此泛型。)
然后,我將展開ResultError
來處理API錯誤以及所有未知錯誤:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
case unknown(Data?, URLResponse?)
}
因此,完成此操作后,您可以更改request
以傳回Result<Data, Error>
:
static func request(urlString: String, parameters: [String: String], completion: @escaping (Result<Data, Error>) -> ()) {
let request = URLRequest(url: URL(string: urlString)!)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let responseData = data, error == nil else {
completion(.failure(error ?? NetworkRequestError.unknown(data, response)))
return
}
completion(.success(responseData))
}
task.resume()
}
然后,呼叫者將執行以下操作:
request(...) { result in
switch result {
case .failure(let error):
// do something with `error`
case .success(let data):
// do something with `data`
}
}
此Result
泛型的優點在於,它成為您可以在整個代碼中使用的一致模式。 例如,假設您有某種方法Foo
request
返回的Data
中解析出Foo
對象:
func retrieveFoo(completion: @escaping (Result<Foo, Error>) -> Void) {
request(...) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let data):
do {
let responseObject = try JSONDecoder().decode(ResponseObject.self, from: data)
if responseObject.error {
completion(.failure(NetworkRequestError.api(responseObject.status, ApiResultCode.code(for: responseObject.message), responseObject.description)))
return
}
let foo = responseObject.foo
completion(.success(foo))
} catch {
completion(.failure(error))
}
}
}
}
或者,如果您想測試特定的API錯誤,例如.recordNotFound
:
retrieveFoo { result in
switch result {
case .failure(NetworkRequestError.api(_, .recordNotFound, _)):
// handle specific “record not found” error here
case .failure(let error):
// handle all other errors here
case .success(let foo):
// do something with `foo`
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.