florianpfisterer florianpfisterer - 7 months ago 225
Swift Question

Asynchronous Callback in NSOperation inside of NSOperationQueue is never called

I need to chain together several

NSOperation
s that do network calls in one
NSOperationQueue
and then wait for all to be completed.

I'm adding all of the
NSOperation
s 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!

Answer

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.