简体   繁体   English

URLSession.shared.dataTask接收数据的正确方法

[英]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: 在哪里将响应编码到称为ApiResultCodeenum 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 如果您愿意进行更广泛的重新设计,我个人建议

  • refactor 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对象以确定失败的原因);
  • but have this new Result enumeration include associated values, namely the Data on success and the Error on failure; 但此新的Result枚举包含关联的值,即成功Data和失败Error and
  • now that the enumeration includes what we need in its associated values, we can completely eliminate 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.

相关问题 在URLSession.shared.dataTask期间从异步关闭存储数据 - Storing data from an asynchronous closure during URLSession.shared.dataTask 在URLSession.shared.dataTask和ViewController之间共享数据 - Sharing data between URLSession.shared.dataTask and ViewController 在URLSession.shared.dataTask之后调用performSegue - Call performSegue after URLSession.shared.dataTask URLSession.shared.dataTask 冻结 UI - URLSession.shared.dataTask freezes the UI 在URLSession.shared.dataTask中执行performSegueWithIdentifier(with:url) - performSegueWithIdentifier while in URLSession.shared.dataTask(with: url) iOS:来自 URLSession.shared.dataTask 的数据(带有:url 始终为零(在 xcode 调试器中显示 0 字节错误?) - iOS: Data from URLSession.shared.dataTask(with: url is always nil (display 0 bytes bug in xcode debugger?) 命中Node Express端点时URLSession.shared.dataTask数据始终为空 - URLSession.shared.dataTask Data is Always Empty When Hitting Node Express Endpoint 如何获取请求中标头的值(URLSession.shared.dataTask(with:request){(数据,响应,错误) - How get value of headers in request (URLSession.shared.dataTask(with: request) { (data, response, error) 当应用程序处于后台时调用URLSession.shared.dataTask - Invoke URLSession.shared.dataTask when the app is background URLSession.shared.dataTask的完成处理程序内部的闭包 - closure inside completion handler of URLSession.shared.dataTask
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM