Swift 2 中处理异步闭包错误的最佳方法?
Best way to handle errors from async closures in Swift 2?
我使用了很多异步网络请求(顺便说一句,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 的闭包中抛出,因为它们没有抛出支持和 运行 async.
我只看到添加到我的完成块 NSError
返回的方法,但据我所知,使用 NSError
是旧式 Objective-C 方式。 ErrorType
只能与 throws (afaik) 一起使用。
使用 Apple 网络关闭时处理错误的最佳和最现代的方法是什么?据我所知,任何异步网络功能都无法使用 use throws?
有很多方法可以解决这个问题,但我建议使用一个需要 Result Enum 的完成块。这可能是最 'Swift' 的方式。
结果枚举恰好有两种状态,成功和错误,这比通常的两个可选 return 值(数据和错误)有 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()
}
因为 return 值是一个枚举,你应该关闭它。
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)
有一种使用 JavaScript-like Promise 库或 Scala-like "Future and Promise" 库的优雅方法。
使用 Scala-style 期货和承诺,它可能如下所示:
你原来的函数
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ())
可以如下实现。它还展示了如何创建一个承诺,return 尽早失败的未来以及如何 fulfill/reject 一个承诺:
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)")
}
它与 非常相似,
但是有了 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
我使用了很多异步网络请求(顺便说一句,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 的闭包中抛出,因为它们没有抛出支持和 运行 async.
我只看到添加到我的完成块 NSError
返回的方法,但据我所知,使用 NSError
是旧式 Objective-C 方式。 ErrorType
只能与 throws (afaik) 一起使用。
使用 Apple 网络关闭时处理错误的最佳和最现代的方法是什么?据我所知,任何异步网络功能都无法使用 use throws?
有很多方法可以解决这个问题,但我建议使用一个需要 Result Enum 的完成块。这可能是最 'Swift' 的方式。
结果枚举恰好有两种状态,成功和错误,这比通常的两个可选 return 值(数据和错误)有 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()
}
因为 return 值是一个枚举,你应该关闭它。
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)
有一种使用 JavaScript-like Promise 库或 Scala-like "Future and Promise" 库的优雅方法。
使用 Scala-style 期货和承诺,它可能如下所示:
你原来的函数
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ())
可以如下实现。它还展示了如何创建一个承诺,return 尽早失败的未来以及如何 fulfill/reject 一个承诺:
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)")
}
它与 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