操作变为isFinished = YES,而不是由它所在的队列启动

时间:2018-01-07 14:03:19

标签: ios swift nsoperation

概述

  • 有一个异步操作子类
  • 将此操作添加到队列中。
  • 我在开始之前取消了此操作。

运行时错误/警告:

SomeOperation went isFinished=YES without being started by the queue it is in

问题:

  1. 这是可以忽略的东西还是严重的东西?
  2. 如何解决这个问题?
  3. 最终提供的变通方法/解决方案是否有效?
  4. 代码:

    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 = falseisFinished = true
    • isExecutingisFinished是已同步的属性KVO

1 个答案:

答案 0 :(得分:7)

关键问题是,当操作不是markAsCompleted时,您的isFinished会触发isExecuting。我建议您只修改markAsCompleted,只有在isExecuting为真的情况下才能执行此操作。这减少了子类执行任何复杂状态测试的负担,以确定它们是否需要转换到isFinished

话虽如此,我在编写可取消的异步操作时会看到三种基本模式:

  1. 如果我正在处理一些模式,其中取消任务将阻止它将执行操作转换为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将明确完成操作本身。

  2. 有时,当您取消某个异步任务时,它会自动为您调用其完成处理程序,在这种情况下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实现。

  3. 第三种情况是您有一些定期检查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,我可以检查一下。

  4. 这些是我通常看到的三种基本isCancelled模式。话虽如此,如果cancelmarkAsCompleted是一个很好的安全检查,那么更新isFinished只会触发isExecuting,以确保您永远不会遇到您所描述的问题。

    顺便说一下,我用于上述示例的AsynchronousOperation如下,改编自Trying to Understand Asynchronous Operation Subclass。顺便说一句,你所谓的markAsCompleted被称为finish,听起来你是通过不同的机制触发isFinishedisExecuting 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 }
        }
    }