如何优雅地处理使用带有闭包的 NSURLSessionTask 的方法中的错误?
Posted
技术标签:
【中文标题】如何优雅地处理使用带有闭包的 NSURLSessionTask 的方法中的错误?【英文标题】:How to elegantly handle errors in methods using NSURLSessionTask with closures? 【发布时间】:2015-12-29 15:30:49 【问题描述】:当我有一个处理与 REST API 通信的类时,我经常有这样的代码,其中有很多方法使用带有闭包的 NSURLSession 任务作为回调。我还给其中一些方法提供了回调闭包,这样我就可以一个接一个地链接不同的 API 调用。我经常从视图控制器中调用这些方法,例如作为按下按钮后的操作或在 viewDidLoad 中。
让我们举一个示例类,它与一个虚构的 API 对话,其中一种方法发送 POST 请求以向 API 注册新用户:
class ApiConnection
var session: NSURLSession!
init()
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.URLCache = NSURLCache.sharedURLCache()
self.session = NSURLSession(configuration: config)
func registerNewUser(user user: String, password: String, callback: (() -> Void)? )
let data = ["email": user, "password": password]
let url = NSURL(string: "https://backend.myapp.com/auth/register/")
let request = NSMutableURLRequest(URL: url!)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
try! request.HTTPBody = NSJSONSerialization.dataWithJSONObject(data, options: NSJSONWritingOptions())
let task = session.dataTaskWithRequest(request)
data, response, error in
if (error != nil)
print("TODO do the right thing here for error: \(error!.localizedDescription)")
return
if let httpResponse = response as? NSHTTPURLResponse
do
let result = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as? NSDictionary
if httpResponse.statusCode == 201
print("registration went successfully")
callback?()
else
print("registration went wrong, not registered new user: \(result)")
catch
print("TODO something went wrong decoding the JSON response")
task.resume()
现在,例如在视图控制器中,我可以使用以下方式调用此方法:
let myAPI = ApiConnection()
myAPI.registerNewUser(user: "email@foo.bar", password: "testing")
// continue here, probably doing the next call
print("user is registered")
到目前为止一切都很好,但是这个例子完全缺乏适当的错误处理(上面代码中的“TODO”,为了显示警告对话框或其他什么。我想知道如何改进我的代码以优雅的方式做到这一点.
当然,我可以在我创建的完成闭包中返回一个错误对象,并在视图控制器中做一些if error != nil
魔术,但这会很丑陋并且对于连续调用会变得很麻烦。
我认为新的 do-try-catch 习惯用法可能是完美的:拥有一个仅在成功情况下执行的完成闭包,除此之外,当出现任何问题时捕获异常,即使有我可以在 APIConnection 类中编写格式良好的错误消息。但是经过一些阅读后,我发现抛出一个闭包并不容易,因为这个闭包可以随时调用,即使在方法本身已经执行之后,这对我来说很有意义,但我不知道如何很好地处理这种情况。
经验丰富的程序员如何处理这个问题?我的 APIConnection 类的整个方法和体系结构是否愚蠢,整个事情可以做得更优雅? Plaese,感谢任何评论。
【问题讨论】:
您是否考虑过使用 AFNetworking? 说实话,还没有。 AFNetworking 如何更好地解决这个问题?在使用 Swift 2 时我应该使用 Alamofire 而不是 AFNetworking 吗? 我建议使用像 BrightFutures 或 FutureLib 这样的“Futures and Promises”库。 【参考方案1】:最适合这类问题的是promises
概念。您在代码中执行的操作很容易被视为管道操作:get_server_respose |解析_json |执行回调。在同步方法中,这将是非常简单的编写并且将提供干净/可读的代码。在异步世界中,事情变得复杂,您很容易迷失在callback hell 中。
使用 Promise,您可以以与管道同步版本非常相似的方式链接异步操作,并且在链的末端只能有一个故障点。
这是您的问题的promise
-d 版本的外观:
let task = session.dataTaskWithRequest(request)
task.promisify.then(response, data in
guard let httpResponse = response as? NSHTTPURLResponse else
return failedPromise(Error(...)
guard response.statusCode == 201 else
return failedPromise(Error(...)
return jsonParsePromise(data);
).then( parsedDict in
print("Registration went successfully")
callback?()
).then(nil, error in
print("An error occured: \(error)")
)
task.resume()
更简洁,像 json 解析这样的任务在一个单独的(可测试的)单元中完成,并且只有一个故障点。
附:你可以更进一步,承诺 registerNewUser
函数本身:
func registerNewUser(user user: String, password: String) -> Promise
//... task setup
return task.promisify.then(response, data in
guard let httpResponse = response as? NSHTTPURLResponse else
return failedPromise(Error(...)
guard response.statusCode == 201 else
return failedPromise(Error(...)
return jsonParsePromise(data);
)
task.resume()
,该函数的客户端可以方便地使用:
registerNewUser(usernameTextfield.text, passwordTextfied.text).then(user in
print("Registration succeeded with user \(user)")
, error in
print("Registration failed with error \(error)")
)
【讨论】:
你在上面的例子中使用 PromiseKit 吗?我真的认为 Promises 的概念很有意义,我想学习使用它,因此我想知道您为 Swift2 推荐的框架。 我目前正在使用我自己的框架之一,CKPromise。它是用 Objective-C 编写的,支持从 Swift 调用,我打算用 Swift 重写它。如果你想了解更多关于 Promise 的知识,也可以阅读我的博客文章 here 看了一圈之后,我现在已经通过使用 PromiseKit 解决了我的问题。 Swift 2 并没有完美地记录它,但效果很好,并且使编程所有类型的异步东西变得非常容易。顺便说一句,我还更改了所有 HTTP 请求以使用 Alamofire 和 SwiftyJSON。真的很不错的组合。以上是关于如何优雅地处理使用带有闭包的 NSURLSessionTask 的方法中的错误?的主要内容,如果未能解决你的问题,请参考以下文章