我将应用程序的API调用集中在一个名为APIService
的类中。来电如下:
// GET: Attempts getconversations API call. Returns Array of Conversation objects or Error
func getConversations(searchString: String = "", completion: @escaping(Result<[Conversation], APIError>) -> Void) {
{...} //setting up URLRequest
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let _ = data
else {
print("ERROR: ", error ?? "unknown error")
completion(.failure(.responseError))
return
}
do {
{...} //define custom decoding strategy
}
let result = try decoder.decode(ResponseMultipleElements<[Conversation]>.self, from: data!)
completion(.success(result.detailresponse.element))
}catch {
completion(.failure(.decodingError))
}
}
dataTask.resume()
}
我正在从应用程序中的任何位置执行API调用,如:
func searchConversations(searchString: String) {
self.apiService.getConversations(searchString: searchString, completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
{...} // do stuff
}
case .failure(let error):
print("An error occured (error.localizedDescription)")
}
})
}
我现在想实现的是,在输入searchString
时,对用户敲击的每个字符执行func searchConversations
。这将很容易,只需根据正在激发的UIPressesEvent
调用func searchConversations
。像这样:
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
{...} // handle special cases
default:
super.pressesEnded(presses, with: event)
searchConversations(searchString: SearchText.text)
}
}
我现在的问题是:每当输入一个新角色时,我都想取消上一个URLSession并启动一个新的URLSession。如何在UIPressesEvent
处理程序内部执行此操作?
基本思想是确保API返回一个稍后可以取消的对象(如果需要(,然后修改搜索例程以确保取消任何挂起的请求(如果有(:
-
首先,让API调用返回
URLSessionTask
对象:@discardableResult func getConversations(searchString: String = "", completion: @escaping(Result<[Conversation], APIError>) -> Void) -> URLSessionTask { ... let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in ... } dataTask.resume() return dataTask }
-
让你的搜索程序跟踪最后一项任务,如果需要,可以取消它:
private weak var previousTask: URLSessionTask? func searchConversations(searchString: String) { previousTask?.cancel() previousTask = apiService.getConversations(searchString: searchString) { result in ... } }
-
我们经常添加一个微小的延迟,这样如果用户快速输入,我们就可以避免许多不必要的网络请求:
private weak var previousTask: URLSessionTask? private weak var delayTimer: Timer? func searchConversations(searchString: String) { previousTask?.cancel() delayTimer?.invalidate() delayTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { [weak self] _ in guard let self = self else { return } self.previousTask = self.apiService.getConversations(searchString: searchString) {result in ... } } }
-
唯一的另一件事是,你可能想更改你的网络请求错误处理程序,这样请求的"取消"就不会像处理错误一样处理了。从
URLSession
的角度来看,取消是一个错误,但从我们的应用程序的角度来说,取消不是一个错误条件,而是一个预期的流程。
您可以通过使用计时器来实现这一点
1( 定义计时器变量
var requestTimer: Timer?
2( 更新searchConversations功能
@objc func searchConversations() {
self.apiService.getConversations(searchString: SearchText.text, completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
{...} // do stuff
}
case .failure(let error):
print("An error occured (error.localizedDescription)")
}
})
}
3( 更新按结束
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
{...} // handle special cases
default:
super.pressesEnded(presses, with: event)
self.requestTimer?.invalidate()
self.requestTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(searchConversations), userInfo: nil, repeats: false)
}
}