Florian Pfisterer
Florian Pfisterer

Reputation: 1265

Asynchronous Callback in NSOperation inside of NSOperationQueue is never called

I need to chain together several NSOperations that do network calls in one NSOperationQueue and then wait for all to be completed.

I'm adding all of the NSOperations to my NSOperationQueue and then call operationQueue.waitUntilAllOperationsAreFinished(). This works, but it waits indefinitely, as the callbacks in the NSOperations, that set the operation's state to 'finished', get never called.

I'm using the following NSOperation subclass:

class ConcurrentOperation: NSOperation
{
    enum State: String
    {
        case Ready, Executing, Finished

        private var keyPath: String
        {
            return "is" + self.rawValue
        }
    }

    var state: State = State.Ready {
        willSet (newValue)
        {
            self.willChangeValueForKey(newValue.keyPath)
            self.willChangeValueForKey(self.state.keyPath)
        }

        didSet
        {
            self.didChangeValueForKey(oldValue.keyPath)
            self.didChangeValueForKey(self.state.keyPath)
        }
    }
}

extension ConcurrentOperation
{
    override var ready: Bool {
        return super.ready && self.state == .Ready
    }

    override var executing: Bool {
        return self.state == .Executing
    }

    override var finished: Bool {
        return self.state == .Finished
    }

    override var concurrent: Bool {
        return true
    }

    override var asynchronous: Bool {
        return true
    }
}

extension ConcurrentOperation
{
    override func start()
    {
        if self.cancelled
        {
            self.state = .Finished
            return
        }

        self.state = .Executing
        self.main()
    }

    override func cancel()
    {
        self.state = .Finished
    }
}

This is based on a Raywenderlich Tutorial and https://gist.github.com/calebd/93fa347397cec5f88233 .

What this should do is tell the NSOperationQueue that only when the main() method in a subclass of ConcurrentOperation sets the state to .Finished it is completed. And this is also fulfilled by the operation.

However, If I construct the following main() method in such a ConcurrentOperation subclass, the NSOperationQueue never stops as the asynchronous part is never called:

override func main()
{
    dispatch_async(dispatch_get_main_queue(), {
        sleep(1)
        self.state = .Finished  // never called!
    })
}

The problem is the same with a Firebase-Query Callback that I use in my app.

I tried to override the concurrent: Bool property and return true, but that doesn't fix it neither.

How can I accomplish that asynchronous tasks are actually executed in my ConcurrentOperation subclass' main() method?

Thank you!

Upvotes: 0

Views: 1172

Answers (1)

Paulw11
Paulw11

Reputation: 114975

If you call waitUntilAllOperationsAreFinished on the main queue then you will block the main queue. Once the main queue is blocked the dispatch_async(dispatch_get_main_queue(), won't be able to execute as you have created a deadlock; The task that needs to execute to unblock the main queue has been dispatched on the blocked main queue, so it will never run and can never run.

You need to dispatch the waitUntilAllOperationsAreFinished on its own queue.

Upvotes: 1

Related Questions