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([=11=]) }.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" 标记中尝试解码每个接收到的带有错误结构的数据是不行的,也许有一些好的方法来实现这个?
您应该有 API 个错误 return 个错误对象。
例如你可以这样做:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
}
将您的回复编码到一个名为 ApiResultCode
的 enum
中,如下所示:
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 错误,你可以 return 那个。例如
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
泛型的美妙之处在于,它成为您可以在整个代码中使用的一致模式。例如,假设您有一些方法将从 request
returned:
的 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`
}
}
美好的一天!
在检查从 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([=11=]) }.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" 标记中尝试解码每个接收到的带有错误结构的数据是不行的,也许有一些好的方法来实现这个?
您应该有 API 个错误 return 个错误对象。
例如你可以这样做:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
}
将您的回复编码到一个名为 ApiResultCode
的 enum
中,如下所示:
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 错误,你可以 return 那个。例如
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
泛型的美妙之处在于,它成为您可以在整个代码中使用的一致模式。例如,假设您有一些方法将从 request
returned:
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`
}
}