Swift 递归/循环NSURLSession异步完成处理程序
我使用的API需要多个请求才能获得搜索结果。它是这样设计的,因为搜索可能需要很长时间(>5分钟)。初始响应会立即返回有关搜索的元数据,该元数据将用于后续请求,直到搜索完成。我不控制APISwift 递归/循环NSURLSession异步完成处理程序,swift,Swift,我使用的API需要多个请求才能获得搜索结果。它是这样设计的,因为搜索可能需要很长时间(>5分钟)。初始响应会立即返回有关搜索的元数据,该元数据将用于后续请求,直到搜索完成。我不控制API 第一个请求是发送到 对此请求的响应包含一个cookie和有关搜索的元数据。此响应中的重要字段是search\u cookie(一个字符串)和search\u completed\u pct(一个Int) 第二个请求是将搜索\u cookie附加到URL的帖子。乙二醇 对第二个请求的响应将包含以下内容之一:
- 第一个请求是发送到
- 对此请求的响应包含一个cookie和有关搜索的元数据。此响应中的重要字段是
(一个字符串)和search\u cookie
(一个Int)search\u completed\u pct
- 第二个请求是将
附加到URL的帖子。乙二醇搜索\u cookie
- 对第二个请求的响应将包含以下内容之一:
- 查询已完成时的搜索结果(aka
==100)search\u completed\u pct
- 元数据关于搜索进度,
是搜索进度,介于0和100之间search\u completed\u pct
- 查询已完成时的搜索结果(aka
- 如果搜索未完成,我希望每5秒发出一次请求,直到搜索完成(aka
==100)search\u completed\u pct
apiObj.sessionSearch(domain) { result in
Log.info!.message("result: \(result)")
})
斯威夫特
func sessionSearch(domain: String, sessionCompletion: (result: SearchResult) -> ()) {
// Make request to /search/ url
let task = session.dataTaskWithRequest(request) { data, response, error in
let searchCookie = parseCookieFromResponse(data!)
********* pseudo code **************
var progress: Int = 0
var results = SearchResults()
while (progress != 100) {
// Make requests to /results/ until search is complete
self.getResults(searchCookie) { searchResults in
progress = searchResults.search_pct_complete
if (searchResults == 100) {
completion(searchResults)
} else {
sleep(5 seconds)
} //if
} //self.getResults()
} //while
********* pseudo code ************
} //session.dataTaskWithRequest(
task.resume()
}
func getResults(cookie: String, completion: (searchResults: NSDictionary) -> ())
let request = buildRequest((domain), url: NSURL(string: ResultsUrl)!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
let theResults = getJSONFromData(data!)
completion(theResults)
}
task.resume()
}
首先,似乎奇怪的是,没有一个带有GET请求的API只返回结果——即使这可能需要几分钟。但是,正如您所提到的,您不能更改API 因此,根据您的描述,我们需要发出一个有效“轮询”服务器的请求。我们这样做,直到检索到一个已完成的
搜索对象
因此,可行的方法将有目的地定义以下函数和类:
从服务器返回的“搜索”对象的协议:
public protocol SearchType {
var searchID: String { get }
var isCompleted: Bool { get }
var progress: Double { get }
var result: AnyObject? { get }
}
在客户端使用具体的结构或类
异步函数,向服务器发出请求以创建搜索对象(您的#1 POST请求):
然后是另一个异步函数,用于获取“搜索”对象,如果该对象已完成,则可能获取结果:
func fetchSearch(searchID: String, completion: (SearchType?, ErrorType?) -> () )
现在,一个异步函数获取某个“searchID”(您的“search_cookie”)的结果,并在内部实现轮询:
func fetchResult(searchID: String, completion: (AnyObject?, ErrorType?) -> () )
fetchResult
的实现现在可能如下所示:
func fetchResult(searchID: String,
completion: (AnyObject?, ErrorType?) -> () ) {
func poll() {
fetchSearch(searchID) { (search, error) in
if let search = search {
if search.isCompleted {
completion(search.result!, nil)
} else {
delay(1.0, f: poll)
}
} else {
completion(nil, error)
}
}
}
poll()
}
func fetchResult(searchID: String,
cancellationToken ct: CancellationToken) -> Future<AnyObject> {
let promise = Promise<AnyObject>()
func poll() {
fetchSearch(searchID, cancellationToken: ct).map { search in
if search.isCompleted {
promise.fulfill(search.result!)
} else {
delay(1.0, cancellationToken: ct) { ct in
if ct.isCancelled {
promise.reject(CancellationError.Cancelled)
} else {
poll()
}
}
}
}
}
poll()
return promise.future!
}
这种方法使用本地函数poll
来实现轮询功能poll
调用fetchSearch
并在完成时检查搜索是否完成。如果没有,它会延迟一定时间,然后再次调用poll
。这看起来像是一个递归调用,但实际上不是,因为再次调用时,poll
已经完成。局部函数似乎适合这种方法
函数delay
只需等待指定的秒数,然后调用提供的闭包<代码>延迟
可以在
后调度_或使用可取消调度计时器(我们需要稍后实施取消)轻松实现
我没有展示如何实现createSearch
和fetchSearch
。这些可以使用第三方网络库轻松实现,也可以基于nsursession
轻松实现
结论:
可能会有点麻烦的是实现错误处理和取消,以及处理所有的完成处理程序。为了以简洁优雅的方式解决这个问题,我建议使用实现“承诺”或“未来”的助手库,或者尝试用Rx解决它
例如,利用“Scala-like”期货的可行实施:
上面包含完整的错误处理。它还不包含取消。您确实需要实现一种取消请求的方法,否则轮询可能不会停止
使用“CancellationToken”实现取消的解决方案可能如下所示:
func fetchResult(searchID: String,
completion: (AnyObject?, ErrorType?) -> () ) {
func poll() {
fetchSearch(searchID) { (search, error) in
if let search = search {
if search.isCompleted {
completion(search.result!, nil)
} else {
delay(1.0, f: poll)
}
} else {
completion(nil, error)
}
}
}
poll()
}
func fetchResult(searchID: String,
cancellationToken ct: CancellationToken) -> Future<AnyObject> {
let promise = Promise<AnyObject>()
func poll() {
fetchSearch(searchID, cancellationToken: ct).map { search in
if search.isCompleted {
promise.fulfill(search.result!)
} else {
delay(1.0, cancellationToken: ct) { ct in
if ct.isCancelled {
promise.reject(CancellationError.Cancelled)
} else {
poll()
}
}
}
}
}
poll()
return promise.future!
}
稍后,您可以取消请求,如下所示:
createSearch().flatMap { search in
fetchResult(search.searchID).map { result in
print(result)
}
}.onFailure { error in
print("Error: \(error)")
}
cr.cancel()
这很有效,谢谢你!我现在将研究承诺作为实现取消的更好方式。@Eric如果您对Scala类期货和如上所示的承诺感兴趣,请看这里:或者,作为替代方案。FutureLib还包含一个独立的取消库。此取消功能可用于完全隐藏基础任务,例如,您的客户端代码不需要引用
NSURLSessionTask
对象即可取消请求。我是《未来图书馆》的作者。
cr.cancel()