[英]URLSession.shared.dataTask correct way to receive data
Good day! 美好的一天!
I am a little confused in trying to find the correct sequence in checking received (data, response, error) from dataTask and doing some special error handling. 我在尝试寻找正确的顺序来检查从dataTask收到的(数据,响应,错误)并进行一些特殊的错误处理时有些困惑。
Usually we have URLSession looking like this: 通常我们的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: "&")
}
}
I have another function that has HTTPRequest inside of it, to wrap everything to and object type I'm working with: 我还有另一个内部带有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
}
Using the code above I can easily create different networkOperation calls for doing different API methods and handle different Data models that are returned. 使用上面的代码,我可以轻松地创建不同的networkOperation调用,以执行不同的API方法并处理返回的不同数据模型。 What I am trying to implement is API Error check.
我正在尝试实现的是API错误检查。 Since my API has some error description for example when you get your APP_ID wrong or current APP_ID has no permission to get information etc.. So if any of these occur the data will look like this:
由于我的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."
}
I think its not alright trying to decode every received data with Error struct in networkOperations "//TODO" mark, maybe there is some good way to implement this? 我认为尝试用networkOperations“ // TODO”标记中的Error结构解码每个接收到的数据是不正确的,也许有一些好的方法可以实现这一点?
You should have your API errors return error objects. 您应该让API错误返回错误对象。
Eg You could do: 例如,您可以这样做:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
}
Where you code your responses into an enum
called ApiResultCode
like so: 在哪里将响应编码到称为
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)
}
}
}
This enum lets you check message
codes without littering your code with string literals. 该枚举使您可以检查
message
代码,而不会在字符串文字中乱扔代码。
And if you parse an API error, you could return that. 而且,如果您解析API错误,则可以返回该错误。 Eg
例如
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
}
If you're open to a broader redesign, I'd personally suggest 如果您愿意进行更广泛的重新设计,我个人建议
RequestResult
to pull out those individual error types (the caller wants to know simply if it succeeded or failed ... if it failed, it should then look at the Error
object to determine why it failed); RequestResult
以提取那些单独的错误类型(调用方只想知道它是成功还是失败...如果失败,则应查看Error
对象以确定失败的原因); Result
enumeration include associated values, namely the Data
on success and the Error
on failure; Result
枚举包含关联的值,即成功Data
和失败Error
; and ReturnedData
. ReturnedData
。 So, first, let's expand that RequestResult
to include the error on failures and the payload on success: 因此,首先,让我们扩展
RequestResult
使其包含失败时的错误和成功时的有效负载:
public enum Result {
case success(Data)
case failure(Error)
}
Actually, modern convention is to make this generic, where the above becomes a Result<Data, Error>
using the following: 实际上,现代惯例是使此通用,上面的代码使用以下内容变为
Result<Data, Error>
:
public enum Result<T, U> {
case success(T)
case failure(U)
}
(Swift 5 actually includes this generic.) (Swift 5实际上包含此泛型。)
And I'd then expand ResultError
to handle both API errors as well as any unknown errors: 然后,我将展开
ResultError
来处理API错误以及所有未知错误:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
case unknown(Data?, URLResponse?)
}
So, having done this, you can change request
to pass back a Result<Data, Error>
: 因此,完成此操作后,您可以更改
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()
}
And the caller would then do: 然后,呼叫者将执行以下操作:
request(...) { result in
switch result {
case .failure(let error):
// do something with `error`
case .success(let data):
// do something with `data`
}
}
The beauty of this Result
generic is that it becomes a consistent pattern you can use throughout your code. 此
Result
泛型的优点在于,它成为您可以在整个代码中使用的一致模式。 For example, let's assume you have some method that is going to parse a Foo
object out of the Data
that request
returned: 例如,假设您有某种方法
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))
}
}
}
}
Or, if you wanted to test for a particular API error, eg .recordNotFound
: 或者,如果您想测试特定的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.