操作变为isFinished = YES,而不是由它所在的队列启动
概观
- 有一个异步操作子类
- 将此操作添加到队列中。
- 我在开始之前取消了这个操作。
运行时错误/警告:
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
setsisExecuting = false
且isFinished = true
-
isExecuting
,isFinished
是同步KVO
属性
关键问题是当操作不是isExecuting
时,你的markAsCompleted
正在触发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
除了取消该任务并调用super之外不需要执行任何操作。 例如: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` } }
在这种情况下,我在一个操作中包装了一个显示链接,所以我可以管理依赖项和/或将显示链接封装在一个方便的对象中,我根本不需要实现
cancel
,因为默认实现将更新isCancelled
对我来说,我可以检查一下。
这些是我通常看到的三种基本cancel
模式。 已经说过,更新markAsCompleted
只触发isFinished
如果isExecuting
是一个很好的安全检查,以确保你永远不会遇到你描述的问题。
顺便说一句,我用于上述示例的AsynchronousOperation
如下所示,改编自试图理解异步操作子类 。 BTW,你所谓的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 { 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 } } }
- 使用自定义animation时,会在iOS9上忽略edgesForExtendedLayout
- 在发布iOS之前的应用链接
- Xcode 6 GM创build存档
- NSPredicate predicateWithFormat:argumentArray:仅评估第一个参数
- 应用程序在iOS和Android上恢复状态
- 为什么UIScrollView暂停我的CADisplayLink?
- 在单独的.swift文件中使用扩展名
- ios的Facebook的SDK的v3.11共享对话框发布button被禁用
- 在endInterruption调用AVAudioSessionDelegate,但不调用beginInterruption