Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait for all Operations in queue to finish before performing task

I have an Operation subclass and Operation queue with maxConcurrentOperationCount = 1.

This performs my operations in a sequential order that i add them which is good but now i need to wait until all operations have finished before running another process.

i was trying to use notification group but as this is run in a for loop as soon as the operations have been added to the queue the notification group fires.. How do i wait for all operations to leave the queue before running another process?

for (index, _) in  self.packArray.enumerated() {

    myGroup.enter()
    let myArrayOperation = ArrayOperation(collection: self.outerCollectionView, id: self.packArray[index].id, count: index)
    myArrayOperation.name = self.packArray[index].id
    downloadQueue.addOperation(myArrayOperation)
    myGroup.leave()

}

myGroup.notify(queue: .main) {
 // do stuff here
}
like image 752
WanderingScouse Avatar asked Feb 27 '17 21:02

WanderingScouse


2 Answers

You can use operation dependencies to initiate some operation upon the completion of a series of other operations:

let queue = OperationQueue()

let completionOperation = BlockOperation {
    // all done
}

for object in objects {
    let operation = ...
    completionOperation.addDependency(operation)
    queue.addOperation(operation)
}

OperationQueue.main.addOperation(completionOperation)  // or, if you don't need it on main queue, just `queue.addOperation(completionOperation)`

Or, in iOS 13 and later, you can use barriers:

let queue = OperationQueue()

for object in objects {
    queue.addOperation(...)
}

queue.addBarrierBlock {
    DispatchQueue.main.async {
        // all done
    }
}
like image 164
Rob Avatar answered Nov 19 '22 16:11

Rob


A suitable solution is KVO

First before the loop add the observer (assuming queue is the OperationQueue instance)

queue.addObserver(self, forKeyPath:"operations", options:.new, context:nil)

Then implement

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if object as? OperationQueue == queue && keyPath == "operations" {
        if queue.operations.isEmpty {
            // Do something here when your queue has completed
            self.queue.removeObserver(self, forKeyPath:"operations")
        }
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

Edit:

In Swift 4 it's much easier

Declare a property:

var observation : NSKeyValueObservation?

and create the observer

observation = queue.observe(\.operationCount, options: [.new]) { [unowned self] (queue, change) in
    if change.newValue! == 0 {
        // Do something here when your queue has completed
        self.observation = nil
    }
}

Since iOS13 and macOS15 operationCount is deprecated. The replacement is to observe progress.completedUnitCount.

Another modern way is to use the KVO publisher of Combine

var cancellable: AnyCancellable?

cancellable = queue.publisher(for: \.progress.completedUnitCount)
    .filter{$0 == queue.progress.totalUnitCount}
    .sink() { _ in 
       print("queue finished") 
       self.cancellable = nil           
    }
like image 8
vadian Avatar answered Nov 19 '22 14:11

vadian