README.md
<p align="center">
<img src="Resources/Banner.png" alt="Queuer Banner">
</p>
[![Build Status](https://travis-ci.com/FabrizioBrancati/Queuer.svg?branch=master)](https://travis-ci.com/FabrizioBrancati/Queuer)
[![Coverage Status](https://coveralls.io/repos/github/FabrizioBrancati/Queuer/badge.svg?branch=master)](https://coveralls.io/github/FabrizioBrancati/Queuer?branch=master)
[![Maintainability](https://api.codeclimate.com/v1/badges/ce03faaf6abe697458ed/maintainability)](https://codeclimate.com/github/FabrizioBrancati/Queuer/maintainability)
[![Codebeat Badge](https://codebeat.co/badges/50844e60-f4f2-4f9f-a688-5ccc976b7c8c)](https://codebeat.co/projects/github-com-fabriziobrancati-queuer-master-9833cda0-af64-433d-a08a-cd0d50d6b579)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/dc5a0e93970b42308ddd333c3bcc22ce)](https://www.codacy.com/manual/FabrizioBrancati/Queuer)
<br>
[![Documentation](https://github.fabriziobrancati.com/documentation/Queuer/badge.svg)](https://github.fabriziobrancati.com/documentation/Queuer/)
[![Swift Package Manager Compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
<br>
[![Version](https://img.shields.io/cocoapods/v/Queuer.svg?style=flat)][Documentation]
[![Language](https://img.shields.io/badge/language-Swift%205.0%20%7C%205.1-orange.svg)](https://swift.org/)
[![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-cc9c00.svg)][Documentation]
[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://github.com/FabrizioBrancati/Queuer/blob/master/LICENSE)
---
<p align="center">
<a href="#features">Features</a> •
<a href="#requirements">Requirements</a> •
<a href="#installing">Installing</a> •
<a href="#usage">Usage</a> •
<a href="#documentation">Documentation</a> •
<a href="#changelog">Changelog</a> •
<a href="#communication">Communication</a> •
<a href="#contributing">Contributing</a> •
<a href="#author">Author</a> •
<a href="#license">License</a>
</p>
---
Features
========
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).<br>
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
- [x] Ability to restore uncompleted operations
- [ ] Improve the state restoration feature
- [ ] Throttling between each automatic operation retry
- [ ] Data layer that every operation inside an `OperationQueue` can access
Requirements
============
| **Swift** | **Xcode** | **Queuer** | **iOS** | **macOS** | **tvOS** | **watchOS** | **Linux** |
|-----------|-------------|---------------|---------|------------|-----------|-------------|-----------|
| 3.1...3.2 | 8.3...9.0 | 1.0.0...1.1.0 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ![✓] |
| 4.0 | 9.0...9.2 | 1.3.0 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ![✓] |
| 4.1 | 9.3...9.4 | 1.3.1...1.3.2 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ![✓] |
| 4.2 | 10.0...10.1 | 2.0.0...2.0.1 | 8.0+ | 10.10+ | 9.0+ | 3.0+ | ![✓] |
| 5.0...5.1 | 10.2...11.2 | 2.1.0...2.1.1 | 8.0+ | 10.10+ | 9.0+ | 3.0+ | ![✓] |
Installing
==========
See [Requirements](https://github.com/FabrizioBrancati/Queuer#requirements) section to check Swift, Xcode, Queuer and OS versions.
### Manual
- Open and build the framework from the project (**Queuer.xcodeproj**)
- Import Queuer.framework into your project
- Import the framework with ```import Queuer```
- Enjoy!
### CocoaPods
- Create a **Podfile** in your **project directory** and write into:
```ruby
platform :ios, '8.0'
xcodeproj 'Project.xcodeproj'
use_frameworks!
pod 'Queuer'
```
- Change **"Project"** with your **real project name**
- Open **Terminal**, go to your **project directory** and type: ```pod install```
- Import the framework with ```import Queuer```
- Enjoy!
### Carthage
- Create a **Cartfile** in your **project directory** and write into:
```ruby
github "FabrizioBrancati/Queuer"
```
- Open **Terminal**, go to **project directory** and type: ```carthage update```
- **Include the created Framework** in your project
- **Add Build Phase** with the following contents:
```sh
/usr/local/bin/carthage copy-frameworks
```
Add the paths to the Queuer framework under **Input Files**
```sh
$(SRCROOT)/Carthage/Build/iOS/Queuer.framework
```
Add the paths to the copied frameworks to the **Output Files**
```sh
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Queuer.framework
```
This script works around an [App Store submission bug](http://www.openradar.me/radar?id=6409498411401216) triggered by universal binaries and ensures that necessary bitcode-related files are copied when archiving
- **(Optional)** Add Build Phase with the following contents
```sh
/usr/local/bin/carthage outdated --xcode-warnings
```
To automatically warn you when one of your dependencies is out of date
- Import the framework with ```import Queuer```
- Enjoy!
### Swift Package Manager
- Create a **Package.swift** file in your **project directory** and write into:
```swift
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "Project",
products: [
.executable(name: "Project", targets: ["Project"])
],
dependencies: [
.package(url: "https://github.com/FabrizioBrancati/Queuer.git", .upToNextMajor(from: "2.0.0"))
],
targets: [
.target(name: "Project", dependencies: ["Queuer"])
]
)
```
- Change **"Project"** with your **real project name**
- Open **Terminal**, go to **project directory** and type: ```swift build```
- Import the framework with ```import Queuer```
- Enjoy!
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)
- [Queue States](https://github.com/FabrizioBrancati/Queuer#queue-states)
- [Asynchronous Operation](https://github.com/FabrizioBrancati/Queuer#asynchronous-operation)
- [Synchronous Operation](https://github.com/FabrizioBrancati/Queuer#synchronous-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)
- [Scheduler](https://github.com/FabrizioBrancati/Queuer#scheduler)
- [Semaphore](https://github.com/FabrizioBrancati/Queuer#semaphore)
- [Queue State Restoration (Beta)](https://github.com/FabrizioBrancati/Queuer#queue-state-restoration-beta)
### Shared Queuer
```swift
Queuer.shared.addOperation(operation)
```
### 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:
- Directly on the `queue`(or `Queuer.shared`):
```swift
queue.addOperation {
/// Your task here
}
```
- Creating a `ConcurrentOperation` with a block:
```swift
let concurrentOperation = ConcurrentOperation { _ in
/// Your task here
}
queue.addOperation(concurrentOperation)
```
- Creating a `SynchronousOperation` with a block:
```swift
let synchronousOperation = SynchronousOperation { _ in
/// Your task here
}
queue.addOperation(synchronousOperation)
```
> We will see how `ConcurrentOperation` and `SynchronousOperation` works later.
### Chained Operations
Chained Operations are `Operation`s that add a dependency each other.<br>
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
}
```
### Queue States
- Cancel all `Operation`s in queue:
```swift
queue.cancelAll()
```
- Pause queue:
```swift
queue.pause()
```
> By calling `pause()` you will not be sure that every `Operation` will be paused.<br>
If the `Operation` is already started it will not be on pause until it's a custom `Operation` that overrides `pause()` function.
- Resume queue:
```swift
queue.resume()
```
> 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:
```swift
queue.waitUntilAllOperationsAreFinished()
```
> This function means that the queue will blocks the current thread until all `Operation`s are finished.
### 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.<br>
You must override `execute()` function and call the `finish()` 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)
```
### Synchronous Operation
There are three methods to create synchronous tasks or even queue:
- Setting `maxConcurrentOperationCount` of the queue to `1`.<br>
By setting that property to `1` you will be sure that only one task at time will be executed.
- Using a `Semaphore` and waiting until a task has finished its job.
- Using a `SynchronousOperation`.<br>
It's a subclass of `ConcurrentOperation` that handles synchronous tasks.<br>
It's not awesome as it seems to be and is always better to create an asynchronous task, but some times it may be useful.
For convenience it has an `init` function with a completion block:
```swift
let synchronousOperation = SynchronousOperation { _ in
/// Your task here
}
synchronousOperation.addToQueue(queue)
```
### Automatically Retry an Operation
An `Operation` is passed to every closure, with it you can set and handle the retry feature.<br>
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`.<br>
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.<br>
An `Operation` is passed to every closure, with it you can set and handle the retry feature.<br>
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.<br>
To let the `Operation` retry your execution closure, you have to call the `retry()` function. If the `retry()` is not called, you may block the entire queue. 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()
```
### 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.<br>
I recommend you 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()
```
### Queue State Restoration (Beta)
To enable the Queue Restoration feature you must use `ConcurrentOperation` with a unique (non-nil) `name` property.
Currently this feature allows you to save the current state (`OperationState`s) of your queue, like: `name`, `progress` and `dependencies`.<br>
The `progress` property allows to save the current state of the `Operation` progress. Update it constantly during the `Operation` execution.<br>
Call `Queuer.state(of: OperationQueue)` or `operationQueue.state()` to get the `QueueStateList` aka: Array of `OperationState`s.<br>
It's up to you save and retrieve this list, and create the queue correctly.
Documentation
=============
Jazzy Generated [Documentation] - 100% Documented
Changelog
=========
To see what has changed in recent versions of Queuer, see the **[CHANGELOG.md](https://github.com/FabrizioBrancati/Queuer/blob/master/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](https://github.com/FabrizioBrancati/Queuer#contributing) section.
Contributing
============
See [CONTRIBUTING.md](https://github.com/FabrizioBrancati/Queuer/blob/master/.github/CONTRIBUTING.md) file.
Author
======
**Fabrizio Brancati**
[Website: https://www.fabriziobrancati.com](https://www.fabriziobrancati.com)
<br>
[Email: fabrizio.brancati@gmail.com](mailto:fabrizio.brancati@gmail.com)
License
=======
Queuer is available under the MIT license. See the **[LICENSE](https://github.com/FabrizioBrancati/Queuer/blob/master/LICENSE)** file for more info.
[Documentation]: https://github.fabriziobrancati.com/documentation/Queuer/
[✓]: Resources/Check.png