【问题标题】:Best way to handle errors from async closures in Swift 2?处理 Swift 2 中异步闭包错误的最佳方法?
【发布时间】:2016-04-29 16:59:58
【问题描述】:

我正在使用大量异步网络请求(顺便说一句,iOS 中的任何网络请求都需要异步)并且我正在寻找更好地处理来自 Apple 的 dataTaskWithRequest 错误的方法,它不支持 throws

我有这样的代码:

func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ()) {
    let request = NSURLRequest(URL: NSURL(string: "http://google.com")!)

    if someData == nil {
        // throw my custom error
    }

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        data, response, error in

        // here I want to handle Apple's error
    }
    task.resume()
}

我需要解析我可能的自定义错误并处理来自dataTaskWithRequest 的可能连接错误。 Swift 2 引入了throws,但你不能从 Apple 的闭包中抛出,因为它们没有抛出支持并且运行异步。

我看到添加到我的完成块 NSError 返回的唯一方法,但据我所知,使用 NSError 是老式的 Objective-C 方式。 ErrorType 只能用于 throws (afaik)。

在使用 Apple 网络关闭时,处理错误的最佳和最现代的方法是什么?据我了解,任何异步网络功能都没有任何用处?

【问题讨论】:

标签: swift asynchronous throw nserror


【解决方案1】:

有很多方法可以解决这个问题,但我建议使用需要 Result Enum 的完成块。这可能是最“迅速”的方式。

结果枚举恰好有两种状态,成功和错误,这比通常的两个可选返回值(数据和错误)有很大的优势,通常会导致 4 种可能的状态。

enum Result<T> {
    case Success(T)
    case Error(String, Int)
}

在完成块中使用结果枚举来完成拼图。

let InvalidURLCode = 999
let NoDataCode = 998
func getFrom(urlString: String, completion:Result<NSData> -> Void) {
    // make sure the URL is valid, if not return custom error
    guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) }

    let request = NSURLRequest(URL: url)
    NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
       // if error returned, extract message and code then pass as Result enum
        guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) }

        // if no data is returned, return custom error
        guard let data = data else { return completion(.Error("No data returned", NoDataCode)) }

        // return success
        completion(.Success(data))
    }.resume()
}

因为返回值是一个枚举,你应该关闭它。

getFrom("http://www.google.com") { result in
    switch result {
    case .Success(let data):
        // handle successful data response here
        let responseString = String(data:data, encoding: NSASCIIStringEncoding)
        print("got data: \(responseString)");
    case .Error(let msg, let code):
        // handle error here
        print("Error [\(code)]: \(msg)")
    }
}

另一种解决方案是传递两个完成块,一个代表成功,一个代表错误。类似于:

func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void)

【讨论】:

    【解决方案2】:

    有一种优雅的方法利用类似 JavaScript 的 Promise 库或类似 Scala 的“Future and Promise”库。

    使用 Scala 风格的期货和承诺,它可能如下所示:

    你原来的功能

    func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -&gt; ())

    可以按如下方式实现。它还展示了如何创建承诺、在失败的未来提前返回以及如何履行/拒绝承诺:

    func sendRequest(someData: MyCustomClass) -> Future<NSData> {
      guard let url = ... else {
        return Future.failure(MySessionError.InvalidURL)  // bail out early with a completed future
      }
      let request = ... // setup request
      let promise = Promise<NSData>()  
      NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
        guard let error = error else { 
          promise.reject(error) // Client error
        }
        // The following assertions should be true, unless error != nil
        assert(data != nil) 
        assert(response != nil)
    
        // We expect HTTP protocol:
        guard let response = response! as NSHTTPURLResponse else {
          promise.reject(MySessionError.ProtocolError)  // signal that we expected HTTP.
        }
    
        // Check status code:
        guard myValidStatusCodeArray.contains(response.statusCode) else {
          let message: String? = ... // convert the response data to a string, if any and if possible
          promise.reject(MySessionError.InvalidStatusCode(statusCode: response.statusCode, message: message ?? ""))
        }
    
        // Check MIME type if given:
        if let mimeType = response.MIMEType {
          guard myValidMIMETypesArray.contains(mimeType) else {
            promise.reject(MySessionError.MIMETypeNotAccepted(mimeType: mimeType))
          }
        } else {
          // If we require a MIMEType - reject the promise.
        }
        // transform data to some other object if desired, can be done in a later, too. 
    
        promise.fulfill(data!)
      }.resume()
    
      return promise.future!
    }
    

    如果请求成功,您可能会期望 JSON 作为响应。

    现在,您可以按如下方式使用它:

    sendRequest(myObject).map { data in 
      return try NSJSONSerialization.dataWithJSONObject(data, options: [])
    }
    .map { object in
       // the object returned from the step above, unless it failed.
       // Now, "process" the object: 
       ...
       // You may throw an error if something goes wrong:
       if failed {
           throw MyError.Failed
       }
    }
    .onFailure { error in
       // We reach here IFF an error occurred in any of the 
       // previous tasks.
       // error is of type ErrorType.
       print("Error: \(error)")
    }
    

    【讨论】:

      【解决方案3】:

      Casey's answer很像, 但是使用 Swift 5,现在我们在标准库中实现了 Result(通用枚举),

      //Don't add this code to your project, this has already been implemented
      //in standard library.
      public enum Result<Success, Failure: Error> {
          case success(Success), failure(Failure)
      }
      

      非常好用,

      URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in
          switch result {
          case let .success(success):
              handleResponse(success.response, data: success.data)
          case let .error(error):
              handleError(error)
          }
      }
      

      https://developer.apple.com/documentation/swift/result

      https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-13
        • 2019-08-04
        • 2018-09-12
        • 2013-10-13
        相关资源
        最近更新 更多