Joh*_*145 4 concurrency grand-central-dispatch ios swift
我正在实施搜索。每个搜索查询都会生成一个 DispatchWorkItem,然后将其排队等待执行。由于用户触发新搜索的速度比前一个搜索完成的速度要快,因此我想在收到新搜索后立即取消前一个搜索。
这是我当前的设置:
var currentSearchJob: DispatchWorkItem?
let searchJobQueue = DispatchQueue(label: QUEUE_KEY)
func updateSearchResults(for searchController: UISearchController) {
let queryString = searchController.searchBar.text?.lowercased() ?? ""
// if there is already an (older) search job running, cancel it
currentSearchJob?.cancel()
// create a new search job
currentSearchJob = DispatchWorkItem() {
self.filter(queryString: queryString)
}
// start the new job
searchJobQueue.async(execute: currentSearchJob!)
}
Run Code Online (Sandbox Code Playgroud)
我知道这dispatchWorkItem.cancel()不会立即终止正在运行的任务。相反,我需要手动检查dispatchWorkItem.isCancelled。dispatchWorkItem但在这种情况下我如何获得正确的对象呢?
如果我只设置currentSearchJob一次,我可以简单地访问该属性,就像在本例中所做的那样。但是,这在这里不适用,因为该属性将在filter()方法完成之前被覆盖。我如何知道哪个实例实际上正在运行我要检查的代码dispatchWorkItem.isCancelled?
理想情况下,我想将新创建的参数DispatchWorkItem作为该方法的附加参数提供filter()。但这是不可能的,因为我会收到Variable used within its own initial value错误。
我是 Swift 的新手,所以我希望我错过了一些东西。非常感谢任何帮助!
诀窍是如何让已调度的任务检查它是否已被取消。我实际上建议考虑OperationQueue方法,而不是直接使用调度队列。
至少有两种方法:
恕我直言,最优雅的是只是子类化Operation,在方法中将您想要的任何内容传递给它init,并在方法中执行工作main:
class SearchOperation: Operation {
private var queryString: String
init(queryString: String) {
self.queryString = queryString
super.init()
}
override func main() {
// do something synchronous, periodically checking `isCancelled`
// e.g., for illustrative purposes
print("starting \(queryString)")
for i in 0 ... 10 {
if isCancelled { print("canceled \(queryString)"); return }
print(" \(queryString): \(i)")
heavyWork()
}
print("finished \(queryString)")
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
}
Run Code Online (Sandbox Code Playgroud)
因为它位于Operation子类中,isCancelled所以隐式引用自身而不是某个 ivar,避免了对其检查内容的任何混淆。您的“开始新查询”代码可以只是说“取消相关操作队列上当前的任何内容,并向该队列添加新操作”:
private var searchQueue: OperationQueue = {
let queue = OperationQueue()
// queue.maxConcurrentOperationCount = 1 // make it serial if you want
queue.name = Bundle.main.bundleIdentifier! + ".backgroundQueue"
return queue
}()
func performSearch(for queryString: String) {
searchQueue.cancelAllOperations()
let operation = SearchOperation(queryString: queryString)
searchQueue.addOperation(operation)
}
Run Code Online (Sandbox Code Playgroud)
我推荐这种方法,因为您最终会得到一个小的内聚对象(操作),它本着单一职责原则的精神很好地封装了您想要执行的工作块。
虽然以下内容不太优雅,但从技术上讲,您也可以使用BlockOperation,它是基于块的,但您可以将操作的创建以及向操作添加闭包分离。使用这种技术,您实际上可以将对操作的引用传递给它自己的闭包:
private weak var lastOperation: Operation?
func performSearch(for queryString: String) {
lastOperation?.cancel()
let operation = BlockOperation()
operation.addExecutionBlock { [weak operation, weak self] in
print("starting \(identifier)")
for i in 0 ... 10 {
if operation?.isCancelled ?? true { print("canceled \(identifier)"); return }
print(" \(identifier): \(i)")
self?.heavyWork()
}
print("finished \(identifier)")
}
searchQueue.addOperation(operation)
lastOperation = operation
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
Run Code Online (Sandbox Code Playgroud)
我只是为了完整起见才提到这一点。我认为Operation子类方法通常是更好的设计。我将用于BlockOperation一次性的东西,但一旦我想要更复杂的取消逻辑,我认为Operation子类方法更好。
我还应该提到,除了更优雅的取消功能之外,Operation对象还提供各种其他复杂的功能(例如,异步管理本身异步的任务队列;限制并发程度等)。这一切都超出了这个问题的范围。