Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.

**Configuration**
OS and Version: [e.g. iOS 17.4]
Queuer Version: [e.g. 3.0.0]
OS and Version: [e.g. iOS 18.4]
Queuer Version: [e.g. 3.1.0]

**Additional Context**
Add any other context about the problem here.
2 changes: 1 addition & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ let package = Package(
.library(name: "Queuer", targets: ["Queuer"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0")
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.0")
],
targets: [
.target(name: "Queuer"),
.target(
name: "Queuer",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]),
.testTarget(name: "QueuerTests", dependencies: ["Queuer"])
]
)
50 changes: 50 additions & 0 deletions Package@swift-6.0.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// swift-tools-version:6.0
//
// Package.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 PackageDescription

let package = Package(
name: "Queuer",
platforms: [
.iOS(.v12),
.macOS(.v10_13),
.macCatalyst(.v13),
.tvOS(.v12),
.watchOS(.v4),
.visionOS(.v1)
],
products: [
.library(name: "Queuer", targets: ["Queuer"])
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.0")
],
targets: [
.target(name: "Queuer"),
.testTarget(name: "QueuerTests", dependencies: ["Queuer"])
]
)
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ Queuer is a queue manager built on top of [OperationQueue](https://developer.app

## 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...3.0.1 | 12.0+ | 10.13+ | 13.0+ | 12.0+ | 4.0+ | 1.0+ | ✅ |
| **Swift** | **Queuer** | **iOS** | **macOS** | **macCatalyst** | **tvOS** | **watchOS** | **visionOS** | **Linux** | **Windows** |
|------------|---------------|---------|------------|-----------------|-----------|-------------|--------------|-----------|-------------|
| 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...3.0.1 | 12.0+ | 10.13+ | 13.0+ | 12.0+ | 4.0+ | 1.0+ | ✅ | |
| 6.0 | 3.1.0 | 12.0+ | 10.13+ | 13.0+ | 12.0+ | 4.0+ | 1.0+ | ✅ | ✅ |

## Installing

Expand All @@ -41,7 +42,7 @@ See [Requirements](https://github.com/FabrizioBrancati/Queuer#requirements) sect
In your `Package.swift` Swift Package Manager manifest, add the following dependency to your `dependencies` argument:

```swift
.package(url: "https://github.com/FabrizioBrancati/Queuer.git", from: "3.0.0"),
.package(url: "https://github.com/FabrizioBrancati/Queuer.git", from: "3.1.0"),
```

Add the dependency to any targets you've declared in your manifest:
Expand Down
187 changes: 187 additions & 0 deletions Sources/Queuer/AsyncConcurrentOperation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//
// ConcurrentOperation.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

/// It allows asynchronous tasks, has a pause and resume states,
/// can be easily added to a queue and can be created with a block.
@available(macOS 10.15, *)
open class AsyncConcurrentOperation: Operation, @unchecked Sendable {
/// `Operation`'s execution block.
public var executionBlock: ((_ operation: AsyncConcurrentOperation) async throws -> Void)?

/// Set if the `Operation` is executing.
private var _executing = false {
willSet {
willChangeValue(forKey: "isExecuting")
}
didSet {
didChangeValue(forKey: "isExecuting")
}
}

/// Set if the `Operation` is executing.
override open var isExecuting: Bool {
return _executing
}

/// Set if the `Operation` is finished.
private var _finished = false {
willSet {
willChangeValue(forKey: "isFinished")
}
didSet {
didChangeValue(forKey: "isFinished")
}
}

/// Set if the `Operation` is finished.
override open var isFinished: Bool {
return _finished
}

/// You should use `success` if you want the retry feature.
/// Set it to `false` if the `Operation` has failed, otherwise `true`.
/// Default is `true` to avoid retries.
open var success = true

/// Maximum allowed retries.
/// Default are 3 retries.
open var maximumRetries = 3

/// Current retry attempt.
open private(set) var currentAttempt = 1

/// Allows for manual retries.
/// If set to `true`, `retry()` function must be manually called.
/// Default is `false` to automatically retry.
open var manualRetry = false

/// Specify if the `Operation` should retry another time.
internal var shouldRetry = true

/// Manually control the `finish(success:)` call of the `Operation`.
/// If set to `true` it is the developer's responsibility to call the `finish(success:)` method,
/// either by passing `false` or `true` to the function.
open var manualFinish = false

/// Keep track of the last executed attempt.
/// This avoids running the `executionBlock` more than once per retry.
private var lastExecutedAttempt = 0

/// Creates the `Operation` with an execution block.
///
/// - Parameters:
/// - name: Operation name.
/// - executionBlock: Execution block.
public init(name: String? = nil, executionBlock: ((_ operation: AsyncConcurrentOperation) async throws -> Void)? = nil) {
super.init()

self.name = name
self.executionBlock = executionBlock
}

/// Start the `Operation`.
override open func start() {
Task {
_executing = true
try await execute()
}
}

/// Retry function.
/// It only works if `manualRetry` property has been set to `true`.
open func retry() async throws {
if manualRetry, shouldRetry, let executionBlock {
try await executionBlock(self)

if !manualFinish {
finish(success: success)
}
}
}

/// Execute the `Operation`.
/// If `executionBlock` is set, it will be executed.
open func execute() async throws {
if let executionBlock {
while shouldRetry, !manualRetry {
if lastExecutedAttempt != currentAttempt {
try await executionBlock(self)
lastExecutedAttempt = currentAttempt
}

if !manualFinish {
finish(success: success)
}
}

try await retry()
}
}

/// Notify the completion of asynchronous task and hence the completion of the `Operation`.
/// Must be called when the `Operation` is finished.
///
/// - Parameter success: Set it to `false` if the `Operation` has failed, otherwise `true`.
/// Default is `true`.
open func finish(success: Bool = true) {
if success || currentAttempt >= maximumRetries {
_executing = false
_finished = true
shouldRetry = false
} else {
currentAttempt += 1
shouldRetry = true
}

self.success = success
}

/// Pause the current `Operation`, if it's supported.
/// Must be overridden by a subclass to get a custom pause action.
open func pause() {}

/// Resume the current `Operation`, if it's supported.
/// Must be overridden by a subclass to get a custom resume action.
open func resume() {}
}

/// `ConcurrentOperation` extension with queue handling.
@available(macOS 10.15, *)
extension AsyncConcurrentOperation {
/// Adds the `Operation` to `shared` Queuer.
public func addToSharedQueuer() {
Queuer.shared.addOperation(self)
}

/// Adds the `Operation` to the custom queue.
///
/// - Parameter queue: Custom queue where the `Operation` will be added.
public func addToQueue(_ queue: Queuer) {
queue.addOperation(self)
}
}
2 changes: 1 addition & 1 deletion Sources/Queuer/ConcurrentOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Foundation

/// It allows asynchronous tasks, has a pause and resume states,
/// can be easily added to a queue and can be created with a block.
open class ConcurrentOperation: Operation {
open class ConcurrentOperation: Operation, @unchecked Sendable {
/// `Operation`'s execution block.
public var executionBlock: ((_ operation: ConcurrentOperation) -> Void)?

Expand Down
4 changes: 2 additions & 2 deletions Sources/Queuer/GroupOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import Foundation

/// It allows the creation of group `Operation`s by using it's `operations` array of `ConcurrentOperation`.
open class GroupOperation: ConcurrentOperation {
open class GroupOperation: ConcurrentOperation, @unchecked Sendable {
/// Private `OperationQueue` instance.
private let queue = OperationQueue()

Expand All @@ -50,7 +50,7 @@ open class GroupOperation: ConcurrentOperation {
/// - Parameters:
/// - operations: Array of ConcurrentOperation to be executed.
/// - completionHandler: Block that will be executed once all operations are over.
public init(_ operations: [ConcurrentOperation], completionHandler: (() -> Void)? = nil) {
public init(_ operations: [ConcurrentOperation], completionHandler: (@Sendable () -> Void)? = nil) {
super.init()

self.operations = operations
Expand Down
Loading
Loading