SomeOperation went isFinished=YES without being started by the queue it is in
public class SomeOperation : AsyncOperation {
//MARK: Start
public override func start() {
isExecuting = true
guard !isCancelled else {
markAsCompleted() //isExecuting = false, isFinished = true
return
}
doSomethingAsynchronously { [weak self] in
self?.markAsCompleted() //isExecuting = false, isFinished = true
}
}
//MARK: Cancel
public override func cancel() {
super.cancel()
markAsCompleted() //isExecuting = false, isFinished = true
}
}
//someOperation is a property in a class
if let someOperation = someOperation {
queue.addOperation(someOperation)
}
//Based on some condition cancelling it
someOperation?.cancel()
public override func cancel() {
isExecuting = true //Just in case the operation was cancelled before starting
super.cancel()
markAsCompleted()
}
markAsCompleted
设置isExecuting = false
和isFinished = true
isExecuting
,isFinished
是已同步的属性KVO
答案 0 :(得分:7)
关键问题是,当操作不是markAsCompleted
时,您的isFinished
会触发isExecuting
。我建议您只修改markAsCompleted
,只有在isExecuting
为真的情况下才能执行此操作。这减少了子类执行任何复杂状态测试的负担,以确定它们是否需要转换到isFinished
。
话虽如此,我在编写可取消的异步操作时会看到三种基本模式:
如果我正在处理一些模式,其中取消任务将阻止它将执行操作转换为isFinished
状态。
在这种情况下,我必须手动完成cancel
实现完成执行操作。例如:
class FiveSecondOperation: AsynchronousOperation {
var block: DispatchWorkItem?
override func main() {
block = DispatchWorkItem { [weak self] in
self?.finish()
self?.block = nil
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: block!)
}
override func cancel() {
super.cancel()
if isExecuting {
block?.cancel()
finish()
}
}
}
专注于cancel
实施,因为如果我取消DispatchWorkItem
它不会完成操作,那么我需要确保cancel
将明确完成操作本身。
有时,当您取消某个异步任务时,它会自动为您调用其完成处理程序,在这种情况下cancel
除了取消该任务和调用之外不需要执行任何操作超。例如:
class GetOperation: AsynchronousOperation {
var url: URL
weak var task: URLSessionTask?
init(url: URL) {
self.url = url
super.init()
}
override func main() {
let task = URLSession.shared.dataTask(with: url) { data, _, error in
defer { self.finish() } // make sure to finish the operation
// process `data` & `error` here
}
task.resume()
self.task = task
}
override func cancel() {
super.cancel()
task?.cancel()
}
}
再次,关注cancel
,在这种情况下,我们不会触及"已完成"状态,但只需取消dataTask
(即使您取消请求也会调用其完成处理程序)并调用super
实现。
第三种情况是您有一些定期检查isCancelled
状态的操作。在这种情况下,您根本不必实施cancel
,因为默认行为就足够了。例如:
class DisplayLinkOperation: AsynchronousOperation {
private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval!
private let duration: CFTimeInterval = 2
override func main() {
startTime = CACurrentMediaTime()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .commonModes)
self.displayLink = displayLink
}
@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let percentComplete = (CACurrentMediaTime() - startTime) / duration
if percentComplete >= 1.0 || isCancelled {
displayLink.invalidate()
finish()
}
// now do some UI update based upon `elapsed`
}
}
在这种情况下,我在操作中包装了显示链接,以便我可以管理依赖关系和/或将显示链接封装在方便的对象中,我不必实现{{1} },因为默认实现会为我更新cancel
,我可以检查一下。
这些是我通常看到的三种基本isCancelled
模式。话虽如此,如果cancel
是markAsCompleted
是一个很好的安全检查,那么更新isFinished
只会触发isExecuting
,以确保您永远不会遇到您所描述的问题。
顺便说一下,我用于上述示例的AsynchronousOperation
如下,改编自Trying to Understand Asynchronous Operation Subclass。顺便说一句,你所谓的markAsCompleted
被称为finish
,听起来你是通过不同的机制触发isFinished
和isExecuting
KVO,但这个想法是基本相同。只需在触发isFinished
KVO:
open class AsynchronousOperation: Operation {
/// State for this operation.
@objc private enum OperationState: Int {
case ready
case executing
case finished
}
/// Concurrent queue for synchronizing access to `state`.
private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent)
/// Private backing stored property for `state`.
private var rawState: OperationState = .ready
/// The state of the operation
@objc private dynamic var state: OperationState {
get { return stateQueue.sync { rawState } }
set { stateQueue.sync(flags: .barrier) { rawState = newValue } }
}
// MARK: - Various `Operation` properties
open override var isReady: Bool { return state == .ready && super.isReady }
public final override var isExecuting: Bool { return state == .executing }
public final override var isFinished: Bool { return state == .finished }
public final override var isAsynchronous: Bool { return true }
// MARK: - KVN for dependent properties
open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if ["isReady", "isFinished", "isExecuting"].contains(key) {
return [#keyPath(state)]
}
return super.keyPathsForValuesAffectingValue(forKey: key)
}
// MARK: - Foundation.Operation
public final override func start() {
if isCancelled {
finish()
return
}
state = .executing
main()
}
/// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.
open override func main() {
fatalError("Subclasses must implement `main`.")
}
/// Call this function to finish an operation that is currently executing
public final func finish() {
if isExecuting { state = .finished }
}
}