我使用的API需要多个请求来获取搜索结果。它是这样设计的,因为搜索可能需要很长时间(> 5分钟)。初始响应立即返回有关搜索的元数据,并且元数据用于后续请求,直到搜索完成。我不控制API。
search_cookie
(字符串)和search_completed_pct
(Int)search_cookie
附加到网址。例如https://api.com/sessions/results/c601eeb7872b7+0 search_completed_pct
== 100)search_completed_pct
是搜索的进度,介于0到100之间。search_completed_pct
== 100)我发现这里有很多类似的帖子,很多都使用Dispatch Groups和for循环,但这种方法对我不起作用。我尝试了一个while循环,并且遇到了变量范围问题。派遣小组也不适合我。这闻起来像是错误的方式,但我不确定。
我正在寻找适当的设计来进行这些递归调用。我应该使用委托还是闭包+循环的方式去?我碰壁了,需要一些帮助。
以下代码是我尝试过的一般概念(为清晰起见而编辑。没有dispatch_groups(),错误处理,json解析等。)
Viewcontroller.swift
apiObj.sessionSearch(domain) { result in
Log.info!.message("result: \(result)")
})
ApiObj.swift
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()
}
答案 0 :(得分:3)
首先,看起来很奇怪,没有带有GET请求的API只返回结果 - 即使这可能需要几分钟。但是,正如您所提到的,您无法更改API。
因此,根据您的描述,我们需要发出一个有效“轮询”服务器的请求。我们这样做,直到我们检索到已完成的Search
对象。
因此,一种可行的方法会故意定义以下函数和类:
从服务器返回的“搜索”对象的协议:
public protocol SearchType {
var searchID: String { get }
var isCompleted: Bool { get }
var progress: Double { get }
var result: AnyObject? { get }
}
在客户端使用具体的结构或类。
一个异步函数,它向服务器发出请求以创建搜索对象(#1 POST请求):
func createSearch(completion: (SearchType?, ErrorType?) -> () )
然后是另一个异步函数,它获取“Search”对象,如果完成则可能得到结果:
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()
}
此方法使用本地函数poll
来实现轮询功能。 poll
调用fetchSearch
,完成后会检查搜索是否完整。如果不是,它会延迟一段时间,然后再次呼叫poll
。这看起来像一个递归调用,但实际上它不是因为poll
已经完成再次调用它。本地函数似乎适合这种方法。
函数delay
只是等待指定的秒数,然后调用提供的闭包。 delay
可以通过dispatch_after
或使用可取消的调度计时器轻松实现(我们稍后需要实现取消)。
我没有展示如何实施createSearch
和fetchSearch
。这些可以使用第三方网络库轻松实现,也可以基于NSURLSession
轻松实现。
<强>结论:强>
可能变得有点麻烦,是实现错误处理和取消,以及处理所有完成处理程序。为了以简洁和优雅的方式解决这个问题,我建议使用一个实现“Promises”或“Futures”的帮助库 - 或尝试用Rx解决它。
例如,利用“Scala-like”期货的可行实施:
func fetchResult(searchID: String) -> Future<AnyObject> {
let promise = Promise<AnyObject>()
func poll() {
fetchSearch(searchID).map { search in
if search.isCompleted {
promise.fulfill(search.result!)
} else {
delay(1.0, f: poll)
}
}
}
poll()
return promise.future!
}
您将开始获得如下所示的结果:
createSearch().flatMap { search in
fetchResult(search.searchID).map { result in
print(result)
}
}.onFailure { error in
print("Error: \(error)")
}
以上内容包含完整的错误处理。它尚未包含取消。你真的需要实现一种取消请求的方法,否则轮询可能不会停止。
使用“CancellationToken”实现取消的解决方案可能如下所示:
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!
}
可能会被称为:
let cr = CancellationRequest()
let ct = cr.token
createSearch(cancellationToken: ct).flatMap { search in
fetchResult(search.searchID, cancellationToken: ct).map { result in
// if we reach here, we got a result
print(result)
}
}.onFailure { error in
print("Error: \(error)")
}
稍后您可以取消请求,如下所示:
cr.cancel()