Dispatch Group Inside Nsoperation - Still Allowing Multiple Operations Despite Maxconcurrentoperationcount = 1

NSOperation and NSOperationQueue with maxConcurrentOperationCount = 1

Based on how I read Apple's documentation, the concurrent property in NSOperation is readonly, and tells us if the operation will run asynchronously or not. If you plan to start operations manually, you need to make the your NSOperation return YES for asynchronous in order to avoid blocking the thread you are starting your operations from. The concurrent property is just used to monitor the state of the operation if you are running them manually

If you are adding your NSOperation to an NSOperationQueue, the queue will ignore the value of the asynchronousproperty, and run operations according to the maxConcurrentOperationCount.

So, to answer your question: If you run all your operations manually, and set asynchronous to YES, the number of operations running in parallel will depend on how big the delay is between each time you call start on your operations, and how long it will take to finish them. If you add them to a queue, your queue will run the operations one by one, as a serial queue.

Wait for all Operations in queue to finish before performing task

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
}
}

NSOperation vs Grand Central Dispatch

GCD is a low-level C-based API that enables very simple use of a task-based concurrency model. NSOperation and NSOperationQueue are Objective-C classes that do a similar thing. NSOperation was introduced first, but as of 10.5 and iOS 2, NSOperationQueue and friends are internally implemented using GCD.

In general, you should use the highest level of abstraction that suits your needs. This means that you should usually use NSOperationQueue instead of GCD, unless you need to do something that NSOperationQueue doesn't support.

Note that NSOperationQueue isn't a "dumbed-down" version of GCD; in fact, there are many things that you can do very simply with NSOperationQueue that take a lot of work with pure GCD. (Examples: bandwidth-constrained queues that only run N operations at a time; establishing dependencies between operations. Both very simple with NSOperation, very difficult with GCD.) Apple's done the hard work of leveraging GCD to create a very nice object-friendly API with NSOperation. Take advantage of their work unless you have a reason not to.

Caveat:
On the other hand, if you really just need to send off a block, and don't need any of the additional functionality that NSOperationQueue provides, there's nothing wrong with using GCD. Just be sure it's the right tool for the job.

How to reload UITableView when OperationsQueue is done with all operations

There are two possible approaches:

  1. Create a new operation that you want to execute when all the others are done, which I'll call the "completion operation". Then every time you create one of the existing individual operations, add that operation as a dependency to your "completion operation". Then, when you're done adding all of your individual operations (and adding each as a dependency to your "completion operation), then add your completion operation to the main queue. It won't fire until all of the other, individual operations are finished.

    For example:

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 4

    let completionOperation = BlockOperation {
    print("done")
    }

    for _ in 0 ..< 10 {
    let operation = ...
    completionOperation.addDependency(operation)
    queue.addOperation(operation)
    }

    OperationQueue.main.addOperation(completionOperation)

    Note, this assumes that you've created your operations carefully so that the operations don't complete until the task inside the individual operations are done. Notably, if your operation is starting a task that is, itself, an asynchronous task (e.g. a network request), make sure that you define the Operation subclass to be an "asynchronous" operation (isAsynchronous returns true) and make sure that it does the isFinished/isExecuting KVO correctly upon completion.

  2. The other approach is to use a dispatch group. Every time you create one of your individual operations, enter the group (not in the operation itself, but as you create those operations and add them to your operation queue). Then, as the last task in your individual operations, leave the group. Then, when you're done adding all of your individual operations, you can make a dispatch group notify, which you can schedule on the main queue. Then, when all of the individual operations, your dispatch group notify block will fire.

    For example:

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 4

    let group = DispatchGroup()

    for _ in 0 ..< 10 {
    group.enter()
    let operation = BlockOperation {
    ...
    group.leave()
    }
    queue.addOperation(operation)
    }

    group.notify(queue: .main) {
    print("done")
    }

I'd lean towards option 1 (staying within the operation queue paradigm), but both approaches would work.



Related Topics



Leave a reply



Submit