我知道曾经有类似的问题要问,但是没有一个 适合我的情况->在下面查看我已经尝试过的尝试。
所以我想做的是制作一个高级searchBar。因此,每次searchParam更改时,我都需要执行代码以检查我的TableViewItems(数组)是否符合条件->过滤器。
smo时。例如键入3个字符,我的代码将检查此字符串。但这需要一段时间才能得到所有结果。
问题: 当smo。然后键入第四个字符,我想停止上一个执行,并使用字符串4来开始一个新的字符。
我的尝试:
使用DispatchWorkItem:
这里的问题是它仅更改一个布尔值,并且代码需要10秒以上的时间才能识别出它已更改。如果我执行它.sync
而不是.async
可以使用,但是它会将应用程序冻结10秒钟以上
使用DispatchQueue:
无法停止,只能暂停->因此将保留在内存中->将保留垃圾邮件
在每个for loop
中检查布尔值:
与DispatchWorkItem相同,将需要10秒钟以上的时间来识别更改
当前代码:(不是很重要)
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.stop = true
if people != nil {
if searchText.isEmpty {
searchedPeople = people
self.stop = false
} else {
self.searchedPeople = []
self.tableView.reloadData()
workItem = DispatchWorkItem(qos: .userInitiated, flags: []) {
outter: for i in 0...searchText.count - 1 {
if self.stop { self.stop = false;break outter } //
if i == 0 {
inner: for person in self.people! {
if self.stop { self.stop = false;break outter } //
let name = (person.Vorname != nil ? person.Vorname! + " " : "") + (person.Nachname ?? "")
if name.lowercased().contains(searchText.lowercased()) {
print("Found: \(name), \(searchText), Exact Match")
self.searchedPeople?.append(person);DispatchQueue.main.async { self.tableView.reloadData()}; continue inner
}
}
} else {
let firstP = searchText.prefix(i+1)
let lastP = searchText.suffix(searchText.count - i + 1)
inner2: for person in self.people! {
if self.stop { self.stop = false;break outter } //
let name = (person.Vorname != nil ? person.Vorname! + " " : "") + (person.Nachname ?? "")
if name.lowercased().contains(firstP.lowercased()) && (name.lowercased().contains(lastP.lowercased())) {
print("Found: \(name), \(searchText), First: \(firstP), Last: \(lastP)")
self.searchedPeople?.append(person)
DispatchQueue.main.async { self.tableView.reloadData()}
continue inner2
}
}
}
}
}
//execute
DispatchQueue.async(execute: workItem)
}
}
}
答案 0 :(得分:3)
从本质上讲,您的问题是,您应该在检查本地状态时检查全局状态。假设您正在进行“ L”操作,而您刚刚键入了“ e”,那么“ Le”操作将开始。大致情况如下:
func updateMyPeepz() {
// At this point, `self.workItem` contains operation 'L'
self.workItem.cancel() // just set isCancelled to true on operation 'L'
self.workItem = DispatchWorkItem { /* bla bla bla */ }
// *now* self.workItem points to operation 'Le'!
}
因此,稍后,在操作“ L”的工作项中,您将执行以下操作:
if self.workItem.isCancelled { /* do something */ }
不幸的是,此代码正在操作'L'中运行,但是self.workItem
现在指向操作'Le'!因此,虽然取消了操作“ L”而没有取消操作“ Le”,但操作“ L”看到了self.workItem
,即“ Le”操作-未取消。因此,检查始终返回false
,并且操作'L'从未真正停止。
使用全局布尔变量时,您会遇到相同的问题,因为它是全局变量,无法区分仍应运行的操作和不应该运行的操作(除了原子性问题之外,您已经在如果您不使用信号量来保护变量,将进行介绍。
解决方法如下:
func updateMyPeepz() {
var workItem: DispatchWorkItem? = nil // This is a local variable
self.currentWorkItem?.cancel() // Cancel the one that was already running
workItem = DispatchWorkItem { /* bla */ } // Set the *local variable* not the property
self.currentWorkItem = workItem // now we set the property
DispatchQueue.global(qos: whatever).async(execute: workItem) // run it
}
现在,这是关键部分。在您的循环中,像这样检查:
if workItem?.isCancelled ?? false // check local workItem, *not* self.workItem!
// (you can also do workItem!.isCancelled if you want, since we can be sure this is non-nil)
在workItem的末尾,将workItem设置为nil
以摆脱保留周期(否则它将泄漏):
workItem = nil // not self.workItem! Nil out *our* item, not the property
或者,您可以将[weak workItem] in
放在workItem
块的顶部,以防止出现循环-则不必在最后将其取消,但是您一定要使用?? false
而非!
,因为您一直想假设弱变量可以随时变为nil。
答案 1 :(得分:1)
编辑:10秒的延迟很可能是由于更新UI时出现的一些问题。我刚刚测试了更新主队列和后台队列上的调度工作项之间的共享布尔值,并且没有明显的传播延迟。但是,对于here和here是否安全存在争议。
self.tableView.reloadData()
发生在工作项的异步块中,也发生在工作项之前的同步部分中...我不确定会产生什么样的行为,因为您依赖顺序幕后的GCD队列中的reloadData
个调用中有一个。如果您的工作项只是构建了一个本地结果数组并且没有与UI进行半直接交互,那将更加可预测。
一个建议,不一定是最好的原因,因为我使用Dispatch已经有一段时间了:让您的工作项找到结果数组,然后更新将搜索字符串映射到结果数组的共享[String: [Person]]
dict(需要锁定) (不确定)。然后,您可以使用DispatchWorkItem.notify
(example)在工作队列结束时在更新UI表的主队列上运行代码,使用与当前键入的搜索字符串匹配的共享字典结果,或者在不执行任何操作的情况下没有结果匹配。
P.S。这是大量的手工工作,我可能会缺少一种更简单的方法。我知道CoreData使用其NSFetchedResultsController
自动执行搜索和更新UI表的任务,但这是完全不同的设置。
答案 2 :(得分:0)
我不确定取消操作是否是您的真正问题。您的搜索代码效率很低。进行重构,使外部循环成为您的人员数组而不是您的搜索字符串,将使操作更快。
您也可以一次计算很多值。
使用下面的代码,我能够在0.32秒(如果将打印出来的“ Found:...”注释掉,则为0.23秒)内从24636个候选者中找到1438个匹配项。
func search(_ people: [Person], for searchText: String) {
guard !people.isEmpty else {
print("No people")
return
}
let lcSearch = searchText.lowercased()
print("Starting search for \(searchText)")
var searchedPeople = [Person]()
for person in people {
let personName = ((person.firstName ?? "") + " " + (person.surname ?? "")).lowercased()
if personName.contains(lcSearch) {
searchedPeople.append(person)
continue
}
for i in 1..<lcSearch.count {
let split = lcSearch.index(lcSearch.startIndex, offsetBy: i)
let firstP = lcSearch.prefix(upTo: split)
let lastP = lcSearch.suffix(from: split)
if personName.contains(firstP) && personName.contains(lastP) {
print("Found: \(personName), \(searchText), First: \(firstP), Last: \(lastP)")
searchedPeople.append(person)
break
}
}
}
print("Done. Found \(searchedPeople.count)")
}