Search code examples
swiftnsoperationqueue

Canceling OperationQueue Swift


I'm learning OperationQueue. What I want to do is to prevent network calls if there's an ongoing call. The problem is that when I tap on the button, a new operation is being added to the queue.

class ViewController: UIViewController {

    private var queue = OperationQueue()

    func networkCall(completion: (()->Void)?) {
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
            completion?()
        }
    }

    lazy var button: UIButton = {
        let b = UIButton(type: .custom)
        b.backgroundColor = .black
        b.setTitleColor(.white, for: .normal)
        b.setTitle("CLICK!~", for: .normal)
        b.addTarget(self, action: #selector(self.boom), for: .touchUpInside)
        return b
    }()

    @objc func boom() {
        print("BOOM!")

        self.queue.maxConcurrentOperationCount = 1

        let block = BlockOperation()
        block.addExecutionBlock {
            self.networkCall {
                print("DONE NETWORK CALL!")
            }
        }

        self.queue.addOperation(block)

        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
            self.queue.cancelAllOperations()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .gray
        self.view.addSubview(self.button)
        self.button.translatesAutoresizingMaskIntoConstraints = false
        self.button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        self.button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        self.button.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
        self.button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
    }
}

Am I missing something?


Solution

  • Unfortunately, you can’t just add an asynchronous method to a BlockOperation. The operation, itself, is going to complete as soon as the asynchronous network request is submitted (even though you’ve deferred the completion?() an arbitrary 3 seconds later).

    Instead, you’re going to want to wrap the network request in a custom, asynchronous, Operation subclass method that does all the KVO notifications for proper Operation behaviors. See the Operation documentation for details.

    See https://stackoverflow.com/a/32322851/1271826 for example with downloadTask implementation. Or see https://stackoverflow.com/a/48104095/1271826 for general discussion.


    By the way, even though you won’t be using BlockOperation, in those cases where you do, be mindful of the addExecutionBlock of BlockOperation. You can, for example, have a serial queue, create a BlockOperation, and then add multiple addExecutionBlock for a given operation, and they will not be serial. Individual operations will behave serially, but not individual blocks you add to a given BlockOperation. I’d generally advise people to avoid addExecutionBlock with BlockOperation until you really grok what’s going on there. Better to just use the main closure when you initialize BlockOperation.