Skip to content

Instantly share code, notes, and snippets.

@jeudesprits
Last active October 28, 2019 12:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeudesprits/8814279336d38de8167fd2426a63e995 to your computer and use it in GitHub Desktop.
Save jeudesprits/8814279336d38de8167fd2426a63e995 to your computer and use it in GitHub Desktop.
class PSGroupOperation: PSOperation {
private let lock = UnfairLock()
private let internalQueue: PSOperationQueue
private let startingOperation = BlockOperation()
private let finishingOperation = BlockOperation()
private var aggregatedErrors = [Error]()
convenience init(underlyingQueue: DispatchQueue = .global(qos: .default), operations: Operation...) {
self.init(underlyingQueue: underlyingQueue, operations: operations)
}
init(underlyingQueue: DispatchQueue = .global(qos: .default), operations: [Operation]) {
internalQueue = PSOperationQueue()
internalQueue.underlyingQueue = underlyingQueue
internalQueue.isSuspended = true
super.init()
internalQueue.delegate = self
internalQueue.addOperation(startingOperation)
for operation in operations { internalQueue.addOperation(operation) }
}
override func cancel() {
internalQueue.cancelAllOperations()
super.cancel()
}
override func execute() {
internalQueue.isSuspended = false
internalQueue.addOperation(finishingOperation)
}
func addOperation(operation: Operation) {
internalQueue.addOperation(operation)
}
final func aggregate(_ error: Error) {
aggregatedErrors.append(error)
}
func operationDidFinish(_ operation: Operation, with errors: [Error]) { }
}
extension PSGroupOperation: PSOperationQueueDelegate {
final func operationQueue(_: PSOperationQueue, willAdd operation: Operation) {
assert(
!finishingOperation.isFinished && !finishingOperation.isExecuting,
"Сannot add new operations to a group after the group has completed"
)
// Some operation in this group has produced a new operation to execute.
// We want to allow that operation to execute before the group completes,
// so we'll make the finishing operation dependent on this newly-produced operation.
if operation !== finishingOperation { finishingOperation.addDependency(operation) }
// All operations should be dependent on the "startingOperation".
// This way, we can guarantee that the conditions for other operations
// will not evaluate until just before the operation is about to run.
// Otherwise, the conditions could be evaluated at any time, even
// before the internal operation queue is unsuspended.
if operation !== startingOperation { operation.addDependency(startingOperation) }
}
final func operationQueue(
_ operationQueue: PSOperationQueue,
didFinish operation: Operation,
with errors: [Error]
) {
lock.lock()
defer { lock.unlock() }
aggregatedErrors.append(contentsOf: errors)
if operation === finishingOperation {
internalQueue.isSuspended = true
finish(with: aggregatedErrors)
} else if operation !== startingOperation {
operationDidFinish(operation, with: errors)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment