Reputation: 291
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
}
Upvotes: 15
Views: 20150
Reputation: 19
func downloadFiles(files: [String], fromDir path: String, toDir localPath: String = "QuotesData", directoryType: FileManager.SearchPathDirectory = .libraryDirectory, maxConcurrentDownloads: Int = 5, qos: QualityOfService = .userInitiated, completion: @escaping (() -> ())) {
// Create an operation queue and set its QoS
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = maxConcurrentDownloads
operationQueue.qualityOfService = qos // Set the QoS for the entire queue
// Track completed downloads to show progress if necessary
let totalDownloads = files.count
var completedDownloads = 0
// Create and add an operation for each file download
for filename in files {
guard !filename.isEmpty else { continue }
// Create a BlockOperation for each download task
let operation = BlockOperation {
// Replace "&" in the file path if necessary
let params = ["FilePath": path.replacingOccurrences(of: "&", with: "Ã"), "FileName": filename]
// Perform the file download
NetworkManager.shared.downloadFile(params: params, to: localPath, directoryType: directoryType) {
// Track progress and update UI if needed
completedDownloads += 1
print("Completed downloading: \(filename), Progress: \(completedDownloads)/\(totalDownloads)")
if totalDownloads == completedDownloads {
completion()
}
}
}
// Set the QoS for each individual operation (optional, if you need specific control)
operation.qualityOfService = qos
// Add the operation to the queue
operationQueue.addOperation(operation)
}
}
Upvotes: -1
Reputation: 571
I have met this problem, too. Well, instead of NSOperation, the GCD has already provide api: dispatch_group
, for example:
dispatch_group_t myGroup = dispatch_group_create();
// task1
dispatch_group_enter(myGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// excute task1
// ...
// task1 completed,leave group
dispatch_group_leave(myGroup);
});
// task2
dispatch_group_enter(myGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// excute task2
// ...
// task2 completed,leave group
dispatch_group_leave(myGroup);
});
dispatch_group_notify(myGroup, dispatch_get_main_queue(), ^{
// call back on all tasks c finished
NSLog(@"all tasks are finished");
});
// continue other tasks.
Upvotes: -1
Reputation: 285250
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
}
Upvotes: 8
Reputation: 438232
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
}
}
Upvotes: 53
Reputation: 3328
My solution is similar to that of https://stackoverflow.com/a/42496559/452115, but I don't add the completionOperation
in the main OperationQueue but into the queue itself. This works for me:
var a = [Int](repeating: 0, count: 10)
let queue = OperationQueue()
let completionOperation = BlockOperation {
print(a)
}
queue.maxConcurrentOperationCount = 2
for i in 0...9 {
let operation = BlockOperation {
a[i] = 1
}
completionOperation.addDependency(operation)
queue.addOperation(operation)
}
queue.addOperation(completionOperation)
print("Done 🎉")
Upvotes: 0
Reputation: 1002
Code at the end of the queue refer to this link
NSOperation and NSOperationQueue are great and useful Foundation framework tools for asynchronous tasks. One thing puzzled me though: How can I run code after all my queue operations finish? The simple answer is: use dependencies between operations in the queue (unique feature of NSOperation). It's just 5 lines of code solution.
NSOperation dependency trick with Swift it is just easy to implement as this:
extension Array where Element: NSOperation {
/// Execute block after all operations from the array.
func onFinish(block: () -> Void) {
let doneOperation = NSBlockOperation(block: block)
self.forEach { [unowned doneOperation] in doneOperation.addDependency($0) }
NSOperationQueue().addOperation(doneOperation)
}}
Upvotes: 0
Reputation: 9
Set the maximum number of concurrent operations to 1
operationQueue.maxConcurrentOperationCount = 1
then each operation will be executed in order (as if each was dependent on the previous one) and your completion operation will execute at the end.
Upvotes: 0
Reputation: 429
I use the next solution:
private let queue = OperationQueue()
private func addOperations(_ operations: [Operation], completionHandler: @escaping () -> ()) {
DispatchQueue.global().async { [unowned self] in
self.queue.addOperations(operations, waitUntilFinished: true)
DispatchQueue.main.async(execute: completionHandler)
}
}
Upvotes: 2