diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aab87c..80137ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Added - Added CIHelper to run test on Linux but not on CI - [#33](https://github.com/FabrizioBrancati/Queuer/pull/33) +- Added syntactic sugar helpers to create and chain operations easier and faster, more info on how to use it [here](https://github.com/FabrizioBrancati/Queuer#syntactic-sugar) ## [3.0.1](https://github.com/FabrizioBrancati/Queuer/releases/tag/3.0.1) - No Loop No Party diff --git a/Sources/Queuer/Queuer.swift b/Sources/Queuer/Queuer.swift index 4f6ffc7..f5e61b5 100644 --- a/Sources/Queuer/Queuer.swift +++ b/Sources/Queuer/Queuer.swift @@ -112,8 +112,11 @@ public class Queuer { /// Blocks the current thread until all of the receiver’s queued and executing /// `Operation`s finish executing. - public func waitUntilAllOperationsAreFinished() { + /// - Returns: Returns the current `Queuer` instance. + @discardableResult + public func waitUntilAllOperationsAreFinished() -> Queuer { queue.waitUntilAllOperationsAreFinished() + return self } } @@ -185,4 +188,9 @@ public extension Queuer { } addOperation(completionOperation) } + + @available(macOS 10.15, *) + func addBarrier(_ completionHandler: @escaping @Sendable () -> Void) { + queue.addBarrierBlock(completionHandler) + } } diff --git a/Sources/Queuer/SyntacticSugar.swift b/Sources/Queuer/SyntacticSugar.swift new file mode 100644 index 0000000..9dce3e6 --- /dev/null +++ b/Sources/Queuer/SyntacticSugar.swift @@ -0,0 +1,135 @@ +// +// SyntaxSugar.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2024 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +public extension Queuer { + @discardableResult + func add(_ operation: Operation) -> Queuer { + addOperation(operation) + return self + } + + @discardableResult + func maxConcurrentOperationCount(_ count: Int) -> Queuer { + maxConcurrentOperationCount = count + return self + } + + @discardableResult + func qualityOfService(_ quality: QualityOfService) -> Queuer { + qualityOfService = quality + return self + } + + @discardableResult + func completion(_ completion: @escaping () -> Void) -> Queuer { + addCompletionHandler(completion) + return self + } + + @discardableResult + func chained(_ operations: Operation...) -> Queuer { + addChainedOperations(operations) + return self + } + +// @discardableResult +// func chained(_ operations: Queuer...) -> Queuer { +// addChainedOperations(operations) +// return self +// } + + @discardableResult +// func concurrent(to queue: Queuer = self,_ block: @escaping (_ operation: ConcurrentOperation) -> Void) -> Queuer { + func concurrent(_ block: @escaping (_ operation: ConcurrentOperation) -> Void) -> Queuer { + addOperation(ConcurrentOperation(executionBlock: block)) + return self + } + + @discardableResult + func group(_ group: ConcurrentOperation...) -> Queuer { + addOperation(GroupOperation(group)) + return self + } + + @available(macOS 13.0, iOS 16.0, *) + func barrier(_ block: @escaping @Sendable () -> Void) -> Queuer { + addBarrier(block) + return self + } + + @available(macOS 13.0, iOS 16.0, *) + @discardableResult + func asyncWait(_ time: C.Instant.Duration, tolerance: C.Instant.Duration? = nil, clock: C = ContinuousClock()) -> Queuer where C: Clock { + let operation = ConcurrentOperation() + operation.manualFinish = true + operation.manualRetry = true + operation.executionBlock { _ in + Task { + try? await Task.sleep(for: time, tolerance: tolerance, clock: clock) + operation.finish() + } + } + add(operation) + return self + } + + @discardableResult + func syncWait(_ time: TimeInterval) -> Queuer { + let operation = ConcurrentOperation { _ in + Thread.sleep(forTimeInterval: time) + } + add(operation) + return self + } +} + +public extension ConcurrentOperation { + @discardableResult + func manualFinish(_ manualFinish: Bool = true) -> ConcurrentOperation { + self.manualFinish = manualFinish + return self + } + + @discardableResult + func manualRetry(_ manualRetry: Bool = true) -> ConcurrentOperation { + self.manualRetry = manualRetry + return self + } + + @discardableResult + func executionBlock(_ block: @escaping (_ operation: ConcurrentOperation) -> Void) -> ConcurrentOperation { + executionBlock = block + return self + } + + @discardableResult + func maximumRetries(_ retries: Int) -> ConcurrentOperation { + maximumRetries = retries + return self + } +} diff --git a/Tests/QueuerTests/ConcurrentOperationTests.swift b/Tests/QueuerTests/ConcurrentOperationTests.swift index 26fa0f5..f53aa00 100644 --- a/Tests/QueuerTests/ConcurrentOperationTests.swift +++ b/Tests/QueuerTests/ConcurrentOperationTests.swift @@ -112,7 +112,7 @@ final class ConcurrentOperationTests: XCTestCase { if CIHelper.isNotRunningOnCI() { let queue = Queuer(name: "ConcurrentOperationTestChainedRetry") let testExpectation = expectation(description: "Chained Retry") - let order = OrderHelper() + let order = OrderHelper() let concurrentOperation1 = ConcurrentOperation { operation in Task { diff --git a/Tests/QueuerTests/Helpers/OrderHelper.swift b/Tests/QueuerTests/Helpers/OrderHelper.swift index 04206dc..8345f49 100644 --- a/Tests/QueuerTests/Helpers/OrderHelper.swift +++ b/Tests/QueuerTests/Helpers/OrderHelper.swift @@ -26,10 +26,10 @@ import Foundation -actor OrderHelper { - var order: [Int] = [] +actor OrderHelper { + var order: [Element] = [] - func append(_ element: Int) { + func append(_ element: Element) { order.append(element) } } diff --git a/Tests/QueuerTests/SyntacticSugarTests.swift b/Tests/QueuerTests/SyntacticSugarTests.swift new file mode 100644 index 0000000..2c3c826 --- /dev/null +++ b/Tests/QueuerTests/SyntacticSugarTests.swift @@ -0,0 +1,113 @@ +// +// SyntaxSugarTests.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2024 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Queuer +import XCTest + +final class SyntaxSugarTests: XCTestCase { + func testComplexCaseOfSyntaxSugar() { + let testExpectation = expectation(description: "Complex Case Of Syntax Sugar") + + var operations: [String] = [] + + let operation = ConcurrentOperation() + .manualFinish() + .manualRetry() + .maximumRetries(5) + .executionBlock { op in + operations.append("Operation 1") + op.finish() + } + + let operation2 = ConcurrentOperation() + .manualRetry() + .maximumRetries(5) + .executionBlock { _ in + operations.append("Operation 2") + } + + Queuer(name: "Queue") + .maxConcurrentOperationCount(1) + .waitUntilAllOperationsAreFinished() + .qualityOfService(.background) + .concurrent { _ in + operations.append("Concurrent 1") + } + .add( + ConcurrentOperation { _ in + operations.append("Add") + } + ) + .completion { + operations.append("Step 1") + } + .chained(operation, operation2) + .completion { + operations.append("Step 2") + } +// .chained(.concurrent {}, operation2) + .concurrent { _ in + operations.append("Concurrent 2") + } +// .asyncWait(.seconds(1)) + .barrier { +// operations.append("Barrier") + } + .chained( + ConcurrentOperation { _ in + operations.append("Chain 1") + }, + ConcurrentOperation { _ in + operations.append("Chain 2") + } + ) +// here you should have the value from the previous operations + .completion { + operations.append("Step 3") + } + .completion { + operations.append("Step 4") + } + .group( + ConcurrentOperation { _ in + operations.append("Group 1") + }, + ConcurrentOperation { _ in + operations.append("Group 2") + } + ) + .syncWait(1) + .completion { + operations.append("Finished") + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertEqual(operations.count, 14) + XCTAssertNil(error) + } + } +}