操作完成了 isFinished=YES 没有被它所在的队列启动
Operation went isFinished=YES without being started by the queue it is in
概览
- 有一个异步操作子类
- 已将此操作添加到队列中。
- 我在开始之前取消了这个操作。
运行时错误/警告:
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
关键问题是你的markAsCompleted
在操作不是isExecuting
的时候触发了isFinished
。我建议您将 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
,在这种情况下我们不触及"finished"状态,而只是取消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`
}
}
在这种情况下,我在一个操作中包装了显示 link 以便我可以管理依赖项 and/or 将显示 link 封装在一个方便的对象中,我不根本不必实施 cancel
,因为默认实施会为我更新 isCancelled
,我可以检查一下。
这是我通常看到的三种基本 cancel
模式。话虽如此,将 markAsCompleted
更新为仅在 isExecuting
时触发 isFinished
是一个很好的安全检查,以确保您永远不会遇到您描述的问题。
顺便说一句,我在上面的例子中使用的AsynchronousOperation
如下,改编自。顺便说一句,你所谓的 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 {
state = .finished
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 }
}
}
概览
- 有一个异步操作子类
- 已将此操作添加到队列中。
- 我在开始之前取消了这个操作。
运行时错误/警告:
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
关键问题是你的markAsCompleted
在操作不是isExecuting
的时候触发了isFinished
。我建议您将 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
,在这种情况下我们不触及"finished"状态,而只是取消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` } }
在这种情况下,我在一个操作中包装了显示 link 以便我可以管理依赖项 and/or 将显示 link 封装在一个方便的对象中,我不根本不必实施
cancel
,因为默认实施会为我更新isCancelled
,我可以检查一下。
这是我通常看到的三种基本 cancel
模式。话虽如此,将 markAsCompleted
更新为仅在 isExecuting
时触发 isFinished
是一个很好的安全检查,以确保您永远不会遇到您描述的问题。
顺便说一句,我在上面的例子中使用的AsynchronousOperation
如下,改编自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 {
state = .finished
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 }
}
}