Queuer is a queue manager, built on top of OperationQueue and Dispatch (aka GCD). It allows you to create any asynchronous and synchronous task easily, all managed by a queue, with just a few lines.
Here is the list of all the features:
- Works on all Swift compatible platforms (even Linux)
- Easy to use
- Well documented (100% documented)
- Well tested (100% of code coverage)
- Create an operation block
- Create a single operation
- Create chained operations
- Manage a centralized queue
- Create unlimited queue
- Declare how many concurrent operation a queue can handle
- Create semaphores
- Create and handle schedules
- Automatically or manually retry an operation
- Throttling between each automatic operation retry
Swift | Queuer | iOS | macOS | macCatalyst | tvOS | watchOS | visionOS | Linux |
---|---|---|---|---|---|---|---|---|
3.1...3.2 | 1.0.0...1.1.0 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ✅ | ||
4.0 | 1.3.0 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ✅ | ||
4.1 | 1.3.1...1.3.2 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ✅ | ||
4.2 | 2.0.0...2.0.1 | 8.0+ | 10.10+ | 9.0+ | 3.0+ | ✅ | ||
5.0...5.10 | 2.1.0...2.2.0 | 8.0+ | 10.10+ | 9.0+ | 3.0+ | ✅ | ||
5.9...5.10 | 3.0.0 | 12.0+ | 10.13+ | 13.0+ | 12.0+ | 4.0+ | 1.0+ | ✅ |
See Requirements section to check Swift, Queuer, and OS versions.
In your Package.swift
Swift Package Manager manifest, add the following dependency to your dependencies
argument:
.package(url: "https://github.com/FabrizioBrancati/Queuer.git", from: "3.0.0"),
Add the dependency to any targets you've declared in your manifest:
.target(
name: "MyTarget",
dependencies: [
.product(name: "Queuer", package: "Queuer"),
]
),
- Shared Queuer
- Custom Queue
- Create an Operation Block
- Chained Operations
- Group Oprations
- Queue States
- Synchronous Queue
- Asynchronous Operation
- Automatically Retry an Operation
- Manually Retry an Operation
- Manually Finish an Operation
- Async Task in an Operation
- Scheduler
- Semaphore
Queuer offers a shared instance that you can use to add operations to a centralized queue:
Queuer.shared.addOperation(operation)
You can also create a custom queue:
let queue = Queuer(name: "MyCustomQueue")
You can even create a queue by defining the maxConcurrentOperationCount
and the qualityOfService
properties:
let queue = Queuer(name: "MyCustomQueue", maxConcurrentOperationCount: Int.max, qualityOfService: .default)
You have three methods to add an Operation
block.
-
Directly on the
queue
(orQueuer.shared
):queue.addOperation { /// Your task here }
-
Creating a
ConcurrentOperation
with a block:let concurrentOperation = ConcurrentOperation { _ in /// Your task here } queue.addOperation(concurrentOperation)
Note
We will see how ConcurrentOperation
works later.
Chained Operations are Operation
s that add a dependency each other.
They follow the given array order, for example: [A, B, C] = A -> B -> C -> completionBlock
.
let concurrentOperationA = ConcurrentOperation { _ in
/// Your task A here
}
let concurrentOperationB = ConcurrentOperation { _ in
/// Your task B here
}
queue.addChainedOperations([concurrentOperationA, concurrentOperationB]) {
/// Your completion task here
}
You can also add a completionHandler
after the queue creation with:
queue.addCompletionHandler {
/// Your completion task here
}
Group Operations are Operation
s that handles a group of Operation
s with a completion handler.
Allows the execution of a block of Operation
s with a completion handler that will be called once all the operations are finished, for example: [A -> [[B & C & D] -> completionHandler] -> E] -> completionHandler
.
It should usually be used with a Chained Opetation.
let groupOperationA = GroupOperation(
[
ConcurrentOperation { _ in
/// Your task A here
},
ConcurrentOperation { _ in
/// Your task B here
}
]
)
let concurrentOperationC = ConcurrentOperation { _ in
/// Your task C here
}
queue.addChainedOperations([groupOperationA, concurrentOperationC]) {
/// Your completion task here
}
In this case the output will be the following one: [[A & B -> completionHandler] -> C] -> completionHandler
.
There are a few method to handle the queue states.
-
Cancel all
Operation
s in a queue:queue.cancelAll()
-
Pause a queue:
queue.pause()
Warning
By calling pause()
you will not be sure that every Operation
will be paused. If the Operation
is already started it will not be on pause until it's a custom Operation
that overrides pause()
function.
-
Resume a queue:
queue.resume()
Warning
To have a complete pause
and resume
states you must create a custom Operation
that overrides pause()
and resume()
function.
-
Wait until all
Operation
s are finished:queue.waitUntilAllOperationsAreFinished()
Important
This function means that the queue will blocks the current thread until all Operation
s are finished.
Setting the maxConcurrentOperationCount
property of a queue to 1
will make you sure that only one task at a time will be executed.
ConcurrentOperation
is a class created to be subclassed.
It allows synchronous and asynchronous tasks, has a pause and resume states, can be easily added to a queue and can be created with a block.
You can create your custom ConcurrentOperation
by subclassing it.
You must override execute()
function and call the finish(success:)
function inside it, when the task has finished its job to notify the queue.
For convenience it has an init
function with a completion block:
let concurrentOperation = ConcurrentOperation { _ in
/// Your task here
}
concurrentOperation.addToQueue(queue)
An Operation
is passed to every closure, with it you can set and handle the retry feature.
By default the retry feature is disabled, to enable it simply set the success
property to false
. With success
to false
the Operation
will retry until reaches maximumRetries
property value. To let the Operation
know when everything is ok, you must set success
to true
.
With currentAttempt
you can know at which attempt the Operation
is.
let concurrentOperation = ConcurrentOperation { operation in
/// Your task here
if /* Successful */ {
operation.success = true
} else {
operation.success = false
}
}
You can manually retry an Operation
when you think that the execution will be successful.
An Operation
is passed to every closure, with it you can set and handle the retry feature.
By default the manual retry feature is disabled, to enable it simply set the manualRetry
property to true
, you must do this outside of the execution closure. You must also set success
to true
or false
to let the Operation
know when is everything ok, like the automatic retry feature.
To let the Operation
retry your execution closure, you have to call the retry()
function. Be sure to call it at least maximumRetries
times, it is not a problem if you call retry()
more times than is needed, your execution closure will not be executed more times than the maximumRetries
value.
let concurrentOperation = ConcurrentOperation { operation in
/// Your task here
if /* Successful */ {
operation.success = true
} else {
operation.success = false
}
}
concurrentOperation.manualRetry = true
/// Later on your code
concurrentOperation.retry()
Caution
If the retry()
function is not called, you may block the entire queue.
By default the Operation
will finish its job when the execution closure is completed.
This means, if you have an asynchronous task insiede the closure, the Operation
will finish before the task is completed.
If you want to finish it manually, you can set the manualFinish
property to true
and call the finish(success:)
function when the task is completed.
Call the finish(success:)
function with success
parameter to true
or false
to let the Operation
know if the task was successful or not. If you don't explicitly set the success
parameter, it will be set to true
.
let concurrentOperation = ConcurrentOperation { operation in
/// Your asynchonous task here
}
concurrentOperation.manualFinish = true
/// Later on your code in your asynchronous task
concurrentOperation.finish(success: true)
Caution
If you don't call the finish(success:)
function, your queue will be blocked and it will never ends.
If you want to use async/await tasks, you need to set manualFinish
to true
in order to be fully in control of the Operation
lifecycle.
Note
Read more about manually finish an Operation
here.
let concurrentOperation = ConcurrentOperation { operation in
Task {
/// Your asynchonous task here
operation.finish(success: true) // or false
}
/// Your asynchonous task here
}
concurrentOperation.manualFinish = true
Caution
If you don't set manualFinish
to true
, your Operation
will finish before the async task is completed.
A Scheduler
is a struct that uses the GDC's DispatchSourceTimer
to create a timer that can execute functions with a specified interval and quality of service.
let schedule = Scheduler(deadline: .now(), repeating: .seconds(1)) {
/// Your task here
}
You can even create a Scheduler
without the handler and set it later:
var schedule = Scheduler(deadline: .now(), repeating: .seconds(1))
schedule.setHandler {
/// Your task here
}
With timer
property you can access to all DispatchSourceTimer
properties and functions, like cancel()
:
schedule.timer.cancel()
A Semaphore
is a struct that uses the GCD's DispatchSemaphore
to create a semaphore on the function and wait until it finish its job.
Tip
Is recommend to use a defer { semaphore.continue() }
right after the Semaphore
creation and wait()
call.
let semaphore = Semaphore()
semaphore.wait()
defer { semaphore.continue() }
/// Your task here
You can even set a custom timeout, default is .distantFuture
:
semaphore.wait(DispatchTime(uptimeNanoseconds: 1_000_000_000))
It's more useful if used inside an asynchronous task:
let concurrentOperation = ConcurrentOperation {
/// Your task here
semaphore.continue()
}
concurrentOperation.addToQueue(queue)
semaphore.wait()
To see what has changed in recent versions of Queuer, see the CHANGELOG.md file.
- If you need help, open an issue.
- If you found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, see Contributing section.
See CONTRIBUTING.md file.
Queuer is available under the MIT license. See the LICENSE file for more info.