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

时间:2019-03-02 15:17:31

标签: ios swift api error-handling nsurlsessiondatatask

美好的一天!

在检查从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结构解码每个接收到的数据是不正确的,也许有一些好的方法可以实现这一点?

1 个答案:

答案 0 :(得分:3)

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

例如您可以这样做:

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

在将响应编码为enum的{​​{1}}的地方,如下所示:

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) } } } 代码,而不会用字符串文字乱码。

如果您解析API错误,则可以返回该错误。例如

message

如果您愿意进行更广泛的重新设计,我个人建议

  • 重构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>

(Swift 5实际上包含了这个泛型。)

然后我将展开public enum Result<T, U> { case success(T) case failure(U) } 以处理API错误以及所有未知错误:

ResultError

因此,完成此操作后,您可以更改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中解析一个Data对象:

request

或者,如果您想测试特定的API错误,例如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)) } } } }

.recordNotFound