diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8953ec6..5fc4b77 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -45,15 +45,28 @@ The easiest way to test this package on Linux is to use Docker. You can use the docker run --rm --privileged --interactive --tty \ --volume "$(pwd):/src" \ --workdir "/src" \ - swift:5.10 + swift:6.0 ``` +Also, you can use the following tags: + +- Use `swift:6.0` to use the Swift 6.0 version. + - Use `swift:6.0-noble` to use the Swift 6.0 version with Ubuntu 24.04. + - Use `swift:6.0-jammy` to use the Swift 6.0 version with Ubuntu 22.04. + - Use `swift:6.0-focal` to use the Swift 6.0 version with Ubuntu 20.04. +- Use `swift:5.10` to use the Swift 5.10 version. + - Use `swift:5.10-noble` to use the Swift 5.10 version with Ubuntu 24.04. + - Use `swift:5.10-jammy` to use the Swift 5.10 version with Ubuntu 22.04. + - Use `swift:5.10-focal` to use the Swift 5.10 version with Ubuntu 20.04. +- Use `swift:5.9` to use the Swift 5.9 version. + - Use `swift:5.9-noble` to use the Swift 5.9 version with Ubuntu 24.04. + - Use `swift:5.9-jammy` to use the Swift 5.9 version with Ubuntu 22.04. + - Use `swift:5.9-focal` to use the Swift 5.9 version with Ubuntu 20.04. + > [!TIP] -> Use `swift:5.10` to use a specific Swift version. If you want to use the latest version, you can use `swift:latest`. -> -> Use `swift:5.10-jammy` to use the Swift 5.10 version with Ubuntu 22.04 and `swift:5.10-focal` to use the Swift 5.10 version with Ubuntu 20.04. +> If you want to use the latest version, you can use `swift:latest`. -1. Run the following command to run the test suite: +3. Run the following command to run the test suite: ```bash swift test diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5181026..be21e2c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,7 +18,7 @@ _Describe the platforms that your PR supports. If your PR does not support a pla _Make sure you check off the following items. If they cannot be completed, provide a reason._ -- [ ] Added tests -- [ ] Added documentation -- [ ] Added a changelog entry -- [ ] Added to the README the new functionality description +- [ ] Added Unit Tests to cover the new changes +- [ ] Added code documentation with `///` to reflect the new changes +- [ ] Added the new changes to the CHANGELOG.md +- [ ] Added the new functionality description to the README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aab87c..6467caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,12 @@ ### Added - Added CIHelper to run test on Linux but not on CI - [#33](https://github.com/FabrizioBrancati/Queuer/pull/33) +- Added `onPause`, `onResume`, and `onCancel` closures to `ConcurrentOperation` class - [#37](https://github.com/FabrizioBrancati/Queuer/pull/37) + +### Improved + +- Improved usage section in README.md file +- Updated swift-docc-plugin to 1.4.3 ## [3.0.1](https://github.com/FabrizioBrancati/Queuer/releases/tag/3.0.1) - No Loop No Party diff --git a/README.md b/README.md index 1b9f2ea..69f8d55 100644 --- a/README.md +++ b/README.md @@ -57,22 +57,23 @@ Add the dependency to any targets you've declared in your manifest: ## Usage -- [Shared Queuer](https://github.com/FabrizioBrancati/Queuer#shared-queuer) -- [Custom Queue](https://github.com/FabrizioBrancati/Queuer#custom-queue) +- [Using the Shared Queuer](https://github.com/FabrizioBrancati/Queuer#using-the-shared-queuer) +- [Create a Custom Queue](https://github.com/FabrizioBrancati/Queuer#create-a-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 Operations](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) +- [Use Chained Operations](https://github.com/FabrizioBrancati/Queuer#use-chained-operations) +- [Use Group Operations](https://github.com/FabrizioBrancati/Queuer#use-group-operations) +- [Available Queue States](https://github.com/FabrizioBrancati/Queuer#available-queue-states) +- [Control Operation States](https://github.com/FabrizioBrancati/Queuer#control-operation-states) +- [Use a Synchronous Queue](https://github.com/FabrizioBrancati/Queuer#use-a-synchronous-queue) - [Create a Custom Operation](https://github.com/FabrizioBrancati/Queuer#create-a-custom-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-task-in-an-operation) -- [Scheduler](https://github.com/FabrizioBrancati/Queuer#scheduler) -- [Semaphore](https://github.com/FabrizioBrancati/Queuer#semaphore) +- [Set Up a Scheduler](https://github.com/FabrizioBrancati/Queuer#set-up-a-scheduler) +- [Use a Semaphore](https://github.com/FabrizioBrancati/Queuer#use-a-semaphore) -### Shared Queuer +### Using the Shared Queuer Queuer offers a shared instance that you can use to add operations to a centralized queue: @@ -80,7 +81,7 @@ Queuer offers a shared instance that you can use to add operations to a centrali Queuer.shared.addOperation(operation) ``` -### Custom Queue +### Create a Custom Queue You can also create a custom queue: @@ -118,7 +119,7 @@ You have three methods to add an `Operation` block. > [!NOTE] > We will see how `ConcurrentOperation` works later. -### Chained Operations +### Use Chained Operations Chained Operations are `Operation`s that add a dependency each other. @@ -144,7 +145,7 @@ queue.addCompletionHandler { } ``` -### Group Operations +### Use Group Operations Group Operations are `Operation`s that handles a group of `Operation`s with a completion handler. @@ -174,7 +175,7 @@ queue.addChainedOperations([groupOperationA, concurrentOperationC]) { In this case the output will be the following one: `[[A & B -> completionHandler] -> C] -> completionHandler`. -### Queue States +### Available Queue States There are a few method to handle the queue states. @@ -211,7 +212,34 @@ There are a few method to handle the queue states. > [!IMPORTANT] > This function means that the queue will blocks the current thread until all `Operation`s are finished. -### Synchronous Queue +### Control Operation States + +You can control the `ConcurrentOperation` states by calling `pause()`, `resume()`, `cancel()`. + +> [!NOTE] +> If you use a `Queuer` object with all `ConcurrentOperation` objects inside to manage your queue, you should avoid calling them directly on the `ConcurrentOperation` object, but you should call them on the `Queuer` one. The `Queuer` object will handle the `ConcurrentOperation` states automatically. + +1. Pause a `ConcurrentOperation`: + + ```swift + concurrentOperation.pause() + ``` + +2. Resume a `ConcurrentOperation`: + + ```swift + concurrentOperation.resume() + ``` + +3. Cancel a `ConcurrentOperation`: + + ```swift + concurrentOperation.cancel() + ``` + +For convenience, you can set closures to the `ConcurrentOperation` object to handle the states with `onPause`, `onResume`, and `onCancel` properties. They will be called when the `ConcurrentOperation` is paused, resumed, or canceled. + +### Use a 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. @@ -315,7 +343,7 @@ concurrentOperation.manualFinish = true > [!CAUTION] > If you don't set `manualFinish` to `true`, your `Operation` will finish before the async task is completed. -### Scheduler +### Set Up a 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. @@ -340,7 +368,7 @@ With `timer` property you can access to all `DispatchSourceTimer` properties and schedule.timer.cancel() ``` -### Semaphore +### Use a 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. diff --git a/Sources/Queuer/ConcurrentOperation.swift b/Sources/Queuer/ConcurrentOperation.swift index 00977da..9b5d758 100644 --- a/Sources/Queuer/ConcurrentOperation.swift +++ b/Sources/Queuer/ConcurrentOperation.swift @@ -32,6 +32,18 @@ open class ConcurrentOperation: Operation { /// `Operation`'s execution block. public var executionBlock: ((_ operation: ConcurrentOperation) -> Void)? + /// `Operation`'s pause block. + /// This block is called when the `Operation` is paused. + public var onPause: ((_ operation: ConcurrentOperation) -> Void)? + + /// `Operation`'s resume block. + /// This block is called when the `Operation` is resumed. + public var onResume: ((_ operation: ConcurrentOperation) -> Void)? + + /// `Operation`'s resume block. + /// This block is called when the `Operation` is canceled. + public var onCancel: ((_ operation: ConcurrentOperation) -> Void)? + /// Set if the `Operation` is executing. private var _executing = false { willSet { @@ -159,12 +171,23 @@ open class ConcurrentOperation: Operation { } /// Pause the current `Operation`, if it's supported. - /// Must be overridden by a subclass to get a custom pause action. - open func pause() {} + /// It can be overridden to add custom behavior. + open func pause() { + onPause?(self) + } /// Resume the current `Operation`, if it's supported. - /// Must be overridden by a subclass to get a custom resume action. - open func resume() {} + /// It can be overridden to add custom behavior. + open func resume() { + onResume?(self) + } + + /// Cancel the current `Operation`, if it's supported. + /// It can be overridden to add custom behavior. + override open func cancel() { + super.cancel() + onCancel?(self) + } } /// `ConcurrentOperation` extension with queue handling. diff --git a/Sources/Queuer/Queuer.swift b/Sources/Queuer/Queuer.swift index 4f6ffc7..72b5992 100644 --- a/Sources/Queuer/Queuer.swift +++ b/Sources/Queuer/Queuer.swift @@ -84,11 +84,17 @@ public class Queuer { } /// Cancel all `Operation`s in queue. + @available(*, deprecated, message: "Use `cancel()` instead.") public func cancelAll() { + cancel() + } + + /// Cancel all `Operation`s in queue. + public func cancel() { queue.cancelAllOperations() } - /// Pause the queue. + /// Pause all `Operation`s in queue. public func pause() { queue.isSuspended = true @@ -99,7 +105,7 @@ public class Queuer { } } - /// Resume the queue. + /// Resume all `Operation`s in queue. public func resume() { queue.isSuspended = false diff --git a/Tests/QueuerTests/ConcurrentOperationTests.swift b/Tests/QueuerTests/ConcurrentOperationTests.swift index 26fa0f5..f24e987 100644 --- a/Tests/QueuerTests/ConcurrentOperationTests.swift +++ b/Tests/QueuerTests/ConcurrentOperationTests.swift @@ -108,6 +108,7 @@ final class ConcurrentOperationTests: XCTestCase { } } + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func testAsyncChainedRetry() async { if CIHelper.isNotRunningOnCI() { let queue = Queuer(name: "ConcurrentOperationTestChainedRetry")