Contents

fabriziobrancati/queuer

Queuer is a queue manager, built on top of [OperationQueue](https://developer.apple.com/documentation/foundation/operationqueue) and [Dispatch](https://developer.apple.com/documentation/dispatch) (aka GCD). It allows you to create any asynchronous and synchronous task easily, all

Features

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:

  • [x] Works on all Swift compatible platforms (even Linux)
  • [x] Easy to use
  • [x] Well documented (100% documented)
  • [x] Well tested (100% of code coverage)
  • [x] Create an operation block
  • [x] Create a single operation
  • [x] Create chained operations
  • [x] Manage a centralized queue
  • [x] Create unlimited queue
  • [x] Declare how many concurrent operation a queue can handle
  • [x] Create semaphores
  • [x] Create and handle schedules
  • [x] Automatically or manually retry an operation
  • [ ] Throttling between each automatic operation retry

Requirements

| 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+ | ✅ |

Installing

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"),
    ]
),

Usage

- [Shared Queuer](https://github.com/FabrizioBrancati/Queuer#shared-queuer)
- [Custom Queue](https://github.com/FabrizioBrancati/Queuer#custom-queue)
- [Create an Operation Block](https://github.com/FabrizioBrancati/Queuer#create-an-operation-block)
- [Chained Operations](https://github.com/FabrizioBrancati/Queuer#chained-operations)
- [Group Oprations](https://github.com/FabrizioBrancati/Queuer#group-operations)
- [Queue States](https://github.com/FabrizioBrancati/Queuer#queue-states)
- [Synchronous Queue](https://github.com/FabrizioBrancati/Queuer#synchronous-queue)
- [Asynchronous Operation](https://github.com/FabrizioBrancati/Queuer#asynchronous-operation)
- [Automatically Retry an Operation](https://github.com/FabrizioBrancati/Queuer#automatically-retry-an-operation)
- [Manually Retry an Operation](https://github.com/FabrizioBrancati/Queuer#manually-retry-an-operation)
- [Manually Finish an Operation](https://github.com/FabrizioBrancati/Queuer#manually-finish-an-operation)
- [Async Task in an Operation](https://github.com/FabrizioBrancati/Queuer#async-tasks-in-an-operation)
- [Scheduler](https://github.com/FabrizioBrancati/Queuer#scheduler)
- [Semaphore](https://github.com/FabrizioBrancati/Queuer#semaphore)

### Shared Queuer

Queuer offers a shared instance that you can use to add operations to a centralized queue:

```swift
Queuer.shared.addOperation(operation)
```

### Custom Queue

You can also create a custom queue:

```swift
let queue = Queuer(name: "MyCustomQueue")
```

You can even create a queue by defining the `maxConcurrentOperationCount` and the `qualityOfService` properties:

```swift
let queue = Queuer(name: "MyCustomQueue", maxConcurrentOperationCount: Int.max, qualityOfService: .default)
```

### Create an Operation Block

You have three methods to add an `Operation` block.

1. Directly on the `queue`(or `Queuer.shared`):

    ```swift
    queue.addOperation {
        /// Your task here
    }
    ```

2. Creating a `ConcurrentOperation` with a block:

    ```swift
    let concurrentOperation = ConcurrentOperation { _ in
        /// Your task here
    }
    queue.addOperation(concurrentOperation)
    ```

> [!NOTE]
> We will see how `ConcurrentOperation` works later.

### Chained Operations

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`.

```swift
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:

```swift
queue.addCompletionHandler {
    /// Your completion task here
}
```

### Group Operations

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](https://github.com/FabrizioBrancati/Queuer#chained-operations).

```swift
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`.

### Queue States

There are a few method to handle the queue states.

1. Cancel all `Operation`s in a queue:

    ```swift
    queue.cancelAll()
    ```

2. Pause a queue:

    ```swift
    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.

3. Resume a queue:

    ```swift
    queue.resume()
    ```

> [!WARNING]
> To have a complete `pause` and `resume` states you must create a custom `Operation` that overrides `pause()` and `resume()` function.

4. Wait until all `Operation`s are finished:

    ```swift
    queue.waitUntilAllOperationsAreFinished()
    ```

> [!IMPORTANT]
> This function means that the queue will blocks the current thread until all `Operation`s are finished.

### Synchronous Queue

Setting the `maxConcurrentOperationCount` property of a queue to `1` will make you sure that only one task at a time will be executed.

### Asynchronous Operation

`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:

```swift
let concurrentOperation = ConcurrentOperation { _ in
    /// Your task here
}
concurrentOperation.addToQueue(queue)
```

### Automatically Retry an Operation

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.

```swift
let concurrentOperation = ConcurrentOperation { operation in
    /// Your task here
    if /* Successful */ {
      operation.success = true
    } else {
      operation.success = false
    }
}
```

### Manually Retry an Operation

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.

```swift
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.

### Manually Finish an Operation

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`.

```swift
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.

### Async Task in an Operation

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](https://github.com/FabrizioBrancati/Queuer#manually-finish-an-operation).

```swift
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.

### Scheduler

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.

```swift
let schedule = Scheduler(deadline: .now(), repeating: .seconds(1)) {
    /// Your task here
}
```

You can even create a `Scheduler` without the handler and set it later:

```swift
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()`:

```swift
schedule.timer.cancel()
```

### Semaphore

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.

```swift
let semaphore = Semaphore()
semaphore.wait()
defer { semaphore.continue() }
/// Your task here
```

You can even set a custom timeout, default is `.distantFuture`:

```swift
semaphore.wait(DispatchTime(uptimeNanoseconds: 1_000_000_000))
```

It's more useful if used inside an asynchronous task:

```swift
let concurrentOperation = ConcurrentOperation {
    /// Your task here
    semaphore.continue()
}
concurrentOperation.addToQueue(queue)
semaphore.wait()
```

Changelog

To see what has changed in recent versions of Queuer, see the CHANGELOG.md file.

Communication

  • 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.

Contributing

See CONTRIBUTING.md file.

License

Queuer is available under the MIT license. See the LICENSE file for more info.

Package Metadata

Repository: fabriziobrancati/queuer

Default branch: main

README: README.md