代码之家  ›  专栏  ›  技术社区  ›  Konstantin

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

  •  0
  • Konstantin  · 技术社区  · 6 年前

    很好的一天!

    在检查从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有一些错误描述,例如当你得到你的应用程序ID错误或者当前的应用程序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”标记中尝试用错误结构对每个接收到的数据进行解码是不好的,也许有一些好的方法来实现这一点?

    0 回复  |  直到 6 年前
        1
  •  3
  •   Rob Md Fahim Faez Abir    6 年前

    您应该让API错误返回错误对象。

    E、 g.你可以:

    enum NetworkRequestError: Error {
        case api(_ status: Int, _ code: ApiResultCode, _ description: String)
    }
    

    把你的回答编码成 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 论成功 错误 失败时;以及
    • 既然枚举在其关联值中包含了我们需要的内容,那么我们可以完全消除 ReturnedData .

    所以,首先,让我们扩展一下 请求结果 要包含失败时的错误和成功时的有效负载,请执行以下操作:

    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 传回来 结果<数据,错误> :

    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`
        }
    }
    

    它的美丽 结果 泛型是它成为一个可以在整个代码中使用的一致模式。例如,假设有一个方法将解析 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`
        }
    }