The operation go isFinished = YES not running in the queue in which it is located

Overview

  • There is a subclass of asynchronous operation
  • Added this operation to the queue.
  • I canceled this operation before it began.

Runtime Error / Warning:

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

Question:

  • Is it something that can be ignored or is it something serious?
  • How to solve this problem?
  • Is the workaround / solution indicated at the end permissible?

The code:

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
    }
}

Add to queue and cancel:

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

//Based on some condition cancelling it
someOperation?.cancel()

Is this really a solution?

public override func cancel() {

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

    super.cancel()
    markAsCompleted()
}

Note:

  • markAsCompletedsets isExecuting = falseandisFinished = true
  • isExecuting, isFinishedare properties that are synchronizedKVO
+1
source share
1 answer

, markAsCompleted isFinished isExecuting. markAsCompleted isExecuting true. , , , isFinished .

:

  1. - , 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 .

  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()
        }
    }
    

    , 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'
        }
    }
    

    , , / , cancel, isCancelled , .

cancel, . , markAsCompleted isFinished isExecuting , , , .


, AsynchronousOperation , , . , markAsCompleted finish, , isFinished isExecuting , . , isFinished:

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 }
    }
}
+7

Source: https://habr.com/ru/post/1675412/


All Articles