Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Swift 6 - Strict Concurrency #38

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"location" : "https://github.com/swiftlang/swift-docc-plugin",
"state" : {
"revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64",
"version" : "1.4.3"
Expand Down
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