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

use*_*037 2 nsoperation ios swift

概观

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

运行时错误/警告:

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

题:

  1. 这是可以忽略的东西还是严肃的东西?
  2. 怎么解决这个?
  3. 最终提供的变通方法/解决方案是否有效?

码:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

添加到队列并取消:

//someOperation is a property in a class
if let someOperation = someOperation {
    queue.addOperation(someOperation)
}

//Based on some condition cancelling it
someOperation?.cancel()
Run Code Online (Sandbox Code Playgroud)

这是有效的解决方案吗?

public override func cancel() {

    isExecuting = true //Just in case the operation was cancelled before starting

    super.cancel()
    markAsCompleted()
}
Run Code Online (Sandbox Code Playgroud)

注意:

  • markAsCompletedisExecuting = falseisFinished = true
  • isExecuting,isFinished是同步的属性KVO

Rob*_*Rob 9

关键问题是你的操作不会markAsCompleted触发.我建议你只是解决这个问题,如果是真的那样做.这减少了子类执行任何复杂状态测试的负担,以确定它们是否需要转换.isFinishedisExecutingmarkAsCompletedisExecutingisFinished

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

  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()
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    专注于cancel实现,因为如果我取消DispatchWorkItem它将无法完成操作,因此我需要确保cancel将明确地完成操作本身.

  2. 有时,当您取消某个异步任务时,它会自动为您调用其完成处理程序,在这种情况下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()
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    再次,关注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`
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在这种情况下,我在一个操作中包装了一个显示链接,所以我可以管理依赖项和/或将显示链接封装在一个方便的对象中,我根本不需要实现cancel,因为默认实现将为isCancelled我更新,我可以检查一下.

这些是cancel我通常看到的三种基本模式.已经说过,更新markAsCompleted只是触发isFinished是否isExecuting是一个很好的安全检查,以确保你永远不会得到你描述的问题.


顺便AsynchronousOperation说一句,我用于上述示例的内容如下,改编自试图理解异步操作子类.BTW,你所谓markAsCompleted的叫做finish,听起来你是通过不同的机制触发isFinishedisExecutingKVO,但这个想法基本相同.在触发isFinishedKVO 之前,只需检查当前状态:

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 }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Rob在你的`AsynchronousOperation`子类中,如果操作在启动之前被取消,那么它稍后启动,它是否会无法达到`.finished`状态?因为在`start()`中你检查它是否被取消,如果是,则调用`finish()`并返回.但是在`finish()`中,如果当前状态是`.executing`,你只能转到`.finished`状态,这似乎无法达到`.finished`.我认为这只是一个示例代码疏忽,但你已经强调了这个`finish()`行为(在这个答案和相关的行为中),我觉得我一定错过了什么? (3认同)