我使用的 API 需要多次请求才能获取搜索结果。之所以这样设计,是因为搜索可能需要很长时间(> 5 分钟)。初始响应立即返回有关搜索的元数据,该元数据用于后续请求,直到搜索完成。我不控制 API。
search_cookie(a String) 和search_completed_pct(an Int)search_cookie附加到URL。例如https://api.com/sessions/results/c601eeb7872b7+0search_completed_pct== 100)search_completed_pct是搜索的进度,将在 0 到 100 之间。search_completed_pct== 100)我在这里找到了很多类似的帖子,很多帖子都使用了调度组和 for 循环,但这种方法对我不起作用。我尝试了一个 while 循环,但在变量作用域方面遇到了问题。调度组对我也不起作用。这闻起来像是走错了路,但我不确定。
我正在寻找合适的设计来进行这些递归调用。我应该使用委托还是闭包+循环?我碰壁了,需要一些帮助。
下面的代码是我尝试过的总体思路(为了清晰起见进行了编辑。没有 dispatch_groups()、错误处理、json 解析等)
视图控制器.swift
apiObj.sessionSearch(domain) { result in
Log.info!.message("result: \(result)")
})
Run Code Online (Sandbox Code Playgroud)
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()
}
Run Code Online (Sandbox Code Playgroud)
首先,似乎很奇怪没有带有 GET 请求的 API 只返回结果 - 即使这可能需要几分钟。但是,正如您所提到的,您无法更改 API。
因此,根据您的描述,我们需要发出一个有效“轮询”服务器的请求。我们这样做直到我们检索到一个Search已完成的对象。
因此,一种可行的方法是特意定义以下函数和类:
从服务器返回的“搜索”对象的协议:
public protocol SearchType {
var searchID: String { get }
var isCompleted: Bool { get }
var progress: Double { get }
var result: AnyObject? { get }
}
Run Code Online (Sandbox Code Playgroud)
在客户端使用具体的结构或类。
一个异步函数,它向服务器发出请求以创建搜索对象(您的 #1 POST 请求):
func createSearch(completion: (SearchType?, ErrorType?) -> () )
Run Code Online (Sandbox Code Playgroud)
然后是另一个异步函数,它获取一个“搜索”对象,如果它完成则可能是结果:
func fetchSearch(searchID: String, completion: (SearchType?, ErrorType?) -> () )
Run Code Online (Sandbox Code Playgroud)
现在,一个异步函数获取某个“searchID”(你的“search_cookie”)的结果 - 并在内部实现轮询:
func fetchResult(searchID: String, completion: (AnyObject?, ErrorType?) -> () )
Run Code Online (Sandbox Code Playgroud)
的实现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()
}
Run Code Online (Sandbox Code Playgroud)
这种方法使用本地函数poll来实现轮询功能。poll调用fetchSearch并在完成时检查搜索是否完成。如果不是,它会延迟一定的持续时间,然后poll再次调用。这看起来像是一个递归调用,但实际上它poll在再次调用时还没有完成。局部函数似乎适合这种方法。
该函数delay只是等待指定的秒数,然后调用提供的闭包。delay可以很容易地实现dispatch_after或 带有可取消的调度计时器(我们稍后需要实现取消)。
我没有展示如何实现createSearch和fetchSearch。这些可以使用第三方网络库轻松实现,也可以基于NSURLSession.
结论:
可能会变得有点麻烦的是实现错误处理和取消,以及处理所有完成处理程序。为了以简洁优雅的方式解决这个问题,我建议使用一个实现“Promises”或“Futures”的辅助库——或者尝试用 Rx 来解决它。
例如,使用“类 Scala”期货的可行实现:
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!
}
Run Code Online (Sandbox Code Playgroud)
您将开始获得如下所示的结果:
createSearch().flatMap { search in
fetchResult(search.searchID).map { result in
print(result)
}
}.onFailure { error in
print("Error: \(error)")
}
Run Code Online (Sandbox Code Playgroud)
以上包含完整的错误处理。它还不包含取消。您确实需要实现一种取消请求的方法,否则可能无法停止轮询。
使用“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!
}
Run Code Online (Sandbox Code Playgroud)
它可能被称为:
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)")
}
Run Code Online (Sandbox Code Playgroud)
稍后您可以取消请求,如下所示:
cr.cancel()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1498 次 |
| 最近记录: |