From 52de0cf237468738fa4b3a02de225e088fb3f906 Mon Sep 17 00:00:00 2001 From: Florian Friedrich Date: Mon, 2 Oct 2023 18:19:35 +0200 Subject: [PATCH] Add support for Swift 5.9, drop support for previous versions of Swift --- CODEOWNERS => .github/CODEOWNERS | 5 +- .github/dependabot.yml | 21 +- .github/workflows/docs.yml | 238 ++---------------- .github/workflows/enable-auto-merge.yml | 4 +- .github/workflows/swift-test.yml | 164 +++--------- .gitignore | 25 +- Package.resolved | 23 ++ Package.swift | 32 ++- Package@swift-5.6.swift | 43 ---- README.md | 6 +- .../Base Classes/BlockOperation.swift | 7 +- .../Base Classes/GroupOperation.swift | 7 +- .../Base Classes/Operation.swift | 55 ++-- .../Base Classes/OperationQueue.swift | 2 +- .../Condition/OperationCondition.swift | 37 +-- .../Helpers/Synchronized.swift | 3 +- .../Observer/BlockObserver.swift | 13 +- .../Observer/OperationObserver.swift | 12 +- .../Conditions/MutuallyExclusive.swift | 4 - .../GCDOperations/Conditions/Negated.swift | 5 - .../Conditions/NoCancelledDependencies.swift | 5 - .../Conditions/NoFailedDependencies.swift | 5 - Sources/GCDOperations/Conditions/Silent.swift | 6 +- Sources/GCDOperations/Observers/Timeout.swift | 7 +- .../Operations/DelayOperation.swift | 3 +- 25 files changed, 202 insertions(+), 530 deletions(-) rename CODEOWNERS => .github/CODEOWNERS (71%) create mode 100644 Package.resolved delete mode 100644 Package@swift-5.6.swift diff --git a/CODEOWNERS b/.github/CODEOWNERS similarity index 71% rename from CODEOWNERS rename to .github/CODEOWNERS index 706801d..5d6c72c 100644 --- a/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,9 +2,8 @@ * @ffried # Workflow & Deployment related files -.github/* @ffried -.jazzy.yaml @ffried -.codecov.yml @ffried +.github/* @ffried +.codecov.yml @ffried # Project & Source files *.swift @ffried diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d85552d..0bf5a9d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,24 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / open-pull-requests-limit: 10 schedule: - interval: "daily" - time: "08:00" - timezone: "Europe/Berlin" + interval: daily + time: 08:00 + timezone: Europe/Berlin + assignees: + - ffried + reviewers: + - ffried + - package-ecosystem: swift + directory: / + open-pull-requests-limit: 10 + schedule: + interval: daily + time: 08:00 + timezone: Europe/Berlin assignees: - ffried reviewers: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dc9c163..0b5e5e7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,229 +8,19 @@ on: push: branches: [ main ] -jobs: - release-context: - runs-on: ubuntu-latest - outputs: - version-name: ${{ github.ref_name }} - is-latest: ${{ steps.compare-tags.outputs.is-latest }} - steps: - - uses: joutvhu/get-release@v1 - id: latest-release - with: - latest: true - throwing: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Compare tags - id: compare-tags - env: - REF_TYPE: ${{ github.ref_type }} - REF_NAME: ${{ github.ref_name }} - LATEST_TAG: ${{ steps.latest-release.outputs.tag_name }} - run: | - if [ "${REF_TYPE}" == 'tag' ] && [ "${REF_NAME}" == "${LATEST_TAG}" ]; then - echo 'is-latest=true' >> "${GITHUB_OUTPUT}" - else - echo 'is-latest=false' >> "${GITHUB_OUTPUT}" - fi - - spm-context: - runs-on: ubuntu-latest - outputs: - package-dump: ${{ steps.dump-package.outputs.package-dump }} - steps: - - uses: swift-actions/setup-swift@v1.24.0 - with: - swift-version: '5.7' - - uses: actions/checkout@v4 - # We don't use a cache here, because SPM doesn't resolve dependencies when dumping packages. - - name: Dump package - id: dump-package - run: | - delimiter="$(openssl rand -hex 8)" - echo "package-dump<<${delimiter}" >> "${GITHUB_OUTPUT}" - swift package dump-package >> "${GITHUB_OUTPUT}" - echo "${delimiter}" >> "${GITHUB_OUTPUT}" +permissions: + contents: write - generate-docs: - needs: - - release-context - - spm-context - runs-on: ubuntu-latest - strategy: - matrix: - target: ${{ fromJson(needs.spm-context.outputs.package-dump).products.*.targets.* }} - steps: - - uses: swift-actions/setup-swift@v1.24.0 - id: swift-setup - with: - swift-version: '5.7' - - name: Read OS Version - uses: sersoft-gmbh/os-version-action@v3 - id: os-version - - uses: actions/checkout@v4 - - uses: actions/cache@v3 - with: - path: .build - key: ${{ runner.os }}-${{ steps.os-version.outputs.version }}-spm-${{ steps.swift-setup.outputs.version }}-${{ hashFiles('**/Package.resolved') }} - restore-keys: | - ${{ runner.os }}-${{ steps.os-version.outputs.version }}-spm-${{ steps.swift-setup.outputs.version }}- - - uses: sersoft-gmbh/swifty-docs-action@v3 - env: - ENABLE_DOCC_SUPPORT: '1' - DOCC_JSON_PRETTYPRINT: 'YES' - with: - package-version: ${{ needs.release-context.outputs.version-name }} - targets: ${{ matrix.target }} - enable-inherited-docs: true - enable-index-building: false - transform-for-static-hosting: true - hosting-base-path: ${{ github.event.repository.name }}/${{ needs.release-context.outputs.version-name }} - output: ${{ matrix.target }}-docs - - name: Package docs - env: - TARGET: ${{ matrix.target }} - run: tar -cvf "${TARGET}-docs.tar" "${TARGET}-docs" - - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.target }}-docs - path: ${{ matrix.target }}-docs.tar +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true - publish-docs: - needs: - - release-context - - spm-context - - generate-docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: gh-pages - path: repository - - uses: actions/download-artifact@v3 - with: - path: artifacts - - name: Extract tars - run: find artifacts -name '*.tar' -execdir tar -xvf '{}' --strip-components 1 \; -delete - - name: Merge documentations - env: - TARGETS: ${{ join(fromJson(needs.spm-context.outputs.package-dump).products.*.targets.*, ' ') }} - DOCS_BASE_DIR: repository/${{ needs.release-context.outputs.version-name }} - run: | - rm -rf "${DOCS_BASE_DIR}" - is_first=1 - for target in $TARGETS; do - if [ $is_first -eq 1 ]; then - echo "Copying initial documentation for ${target}" - cp -R "artifacts/${target}-docs" "${DOCS_BASE_DIR}" - is_first=0 - else - echo "Merging documentation for ${target}" - cp -R "artifacts/${target}-docs/data/documentation/"* "${DOCS_BASE_DIR}/data/documentation/" - cp -R "artifacts/${target}-docs/documentation/"* "${DOCS_BASE_DIR}/documentation/" - fi - done - echo "Deleting non-mergable metadata.json" - rm -f "${DOCS_BASE_DIR}/metadata.json" - - name: Create version index - working-directory: repository - env: - TARGET_DOCS_DIR: ${{ needs.release-context.outputs.version-name }}/documentation - INDEX_FILE: ${{ needs.release-context.outputs.version-name }}/index.html - BASE_URL: 'https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/${{ needs.release-context.outputs.version-name }}/documentation' - REPO_NAME: ${{ github.event.repository.name }} - run: | - target_count=0 - target_list="" - single_target_name="" - for target in $(ls "${TARGET_DOCS_DIR}"); do - if [ -d "${TARGET_DOCS_DIR}/${target}" ]; then - single_target_name="${target}" - target_count=$((target_count+1)) - target_list="${target_list}
  • ${target} Documentation
  • " - fi - done - if [ ${target_count} -gt 1 ]; then - echo "Found ${target_count} targets. Generating list..." - cat > "${INDEX_FILE}" < - - - ${REPO_NAME} Documentation - - - - - - EOF - else - echo "Found one target. Generating redirect file to target ${single_target_name}" - cat > "${INDEX_FILE}" < - - - ${REPO_NAME} Documentation - - - -

    Redirecting...

    - - - EOF - fi - - name: Create root index - working-directory: repository - env: - REDIRECT_URL: 'https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/latest' - REPO_NAME: ${{ github.event.repository.name }} - run: | - cat > 'index.html' < - - - ${REPO_NAME} Documentation - - - -

    Redirecting...

    - - - EOF - - name: Create latest symlink - if: ${{ needs.release-context.outputs.is-latest }} - working-directory: repository - env: - VERSION_NAME: ${{ needs.release-context.outputs.version-name }} - run: | - rm -f 'latest' - ln -s "${VERSION_NAME}" 'latest' - - name: Determine changes - id: check-changes - working-directory: repository - run: | - if [ -n "$(git status --porcelain)" ]; then - echo 'has-changes=true' >> "${GITHUB_OUTPUT}" - else - echo 'has-changes=false' >> "${GITHUB_OUTPUT}" - fi - - uses: crazy-max/ghaction-github-pages@v4 - if: ${{ steps.check-changes.outputs.has-changes }} - with: - keep_history: true - build_dir: repository - commit_message: Deploy documentation for '${{ needs.release-context.outputs.version-name }}' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - cleanup: - needs: - - generate-docs - - publish-docs - if: ${{ always() }} - runs-on: ubuntu-latest - steps: - - name: Cleanup Artifacts - uses: joutvhu/delete-artifact@v1 +jobs: + generate-and-publish-docs: + uses: sersoft-gmbh/oss-common-actions/.github/workflows/swift-generate-and-publish-docs.yml@main + with: + os: ubuntu + swift-version: '5.9' + organisation: ${{ github.repository_owner }} + repository: ${{ github.event.repository.name }} + pages-branch: gh-pages diff --git a/.github/workflows/enable-auto-merge.yml b/.github/workflows/enable-auto-merge.yml index 6b3a13a..74fbf98 100644 --- a/.github/workflows/enable-auto-merge.yml +++ b/.github/workflows/enable-auto-merge.yml @@ -1,6 +1,8 @@ name: Auto-merge for Dependabot PRs -on: pull_request +on: + pull_request: + branches: [ main ] permissions: contents: write diff --git a/.github/workflows/swift-test.yml b/.github/workflows/swift-test.yml index 6836ab9..be9aa3f 100644 --- a/.github/workflows/swift-test.yml +++ b/.github/workflows/swift-test.yml @@ -6,67 +6,37 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: + variables: + outputs: + max-supported-swift-version: '5.9' + xcode-scheme: GCDOperations-Package + xcode-platform-version: latest + fail-if-codecov-fails: true + runs-on: ubuntu-latest + steps: + - run: exit 0 + test-spm: + needs: variables strategy: matrix: - os: [ macos-12 ] - swift-version: [ '' ] - xcode-version: [ '^13.4' ] - include: - - os: macos-12 - swift-version: '' - xcode-version: '^14.1' - - os: ubuntu-20.04 - swift-version: 5.6 - xcode-version: '' - - os: ubuntu-20.04 - swift-version: 5.7 - xcode-version: '' - - os: ubuntu-22.04 - swift-version: 5.7 - xcode-version: '' - - runs-on: ${{ matrix.os }} - - steps: - - if: ${{ runner.os == 'macOS' }} - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: ${{ matrix.xcode-version }} - - name: Install Swift - if: ${{ runner.os == 'Linux' }} - uses: sersoft-gmbh/swifty-linux-action@v3 - with: - release-version: ${{ matrix.swift-version }} - platform: ${{ matrix.os }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Read OS Version - uses: sersoft-gmbh/os-version-action@v3 - id: os-version - - name: Read Swift Version - uses: sersoft-gmbh/swift-version-action@v3 - id: swift-version - - uses: actions/checkout@v4 - - uses: actions/cache@v3 - with: - path: .build - key: ${{ runner.os }}-${{ steps.os-version.outputs.version }}-spm-${{ steps.swift-version.outputs.version }}-${{ hashFiles('**/Package.resolved') }} - restore-keys: | - ${{ runner.os }}-${{ steps.os-version.outputs.version }}-spm-${{ steps.swift-version.outputs.version }}- - - name: Build & Test - run: swift test -v --parallel --enable-code-coverage - - name: Generate Coverage Files - uses: sersoft-gmbh/swift-coverage-action@v4 - id: coverage-files - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} - fail_ci_if_error: true + os: [ macOS, ubuntu ] + swift-version-offset: [ 0 ] + uses: sersoft-gmbh/oss-common-actions/.github/workflows/swift-test-spm.yml@main + with: + os: ${{ matrix.os }} + max-swift-version: ${{ needs.variables.outputs.max-supported-swift-version }} + swift-version-offset: ${{ matrix.swift-version-offset }} + fail-if-codecov-fails: ${{ fromJson(needs.variables.outputs.fail-if-codecov-fails) }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test-xcode: - runs-on: macos-12 + needs: variables strategy: matrix: platform: @@ -75,76 +45,14 @@ jobs: - iPadOS - tvOS - watchOS - env: - XCODE_SCHEME: GCDOperations-Package - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: ^14.1 - - name: Read OS Version - uses: sersoft-gmbh/os-version-action@v3 - id: os-version - - name: Read Swift Version - uses: sersoft-gmbh/swift-version-action@v3 - id: swift-version - - name: Select destination - id: destination - env: - PLATFORM: ${{ matrix.platform }} - run: | - DESTINATION='' - case "${PLATFORM}" in - 'macOS') DESTINATION='platform=macOS';; - 'iOS') DESTINATION='platform=iOS Simulator,OS=latest,name=iPhone 13 Pro';; - 'iPadOS') DESTINATION='platform=iOS Simulator,OS=latest,name=iPad Pro (11-inch) (3rd generation)';; - 'tvOS') DESTINATION='platform=tvOS Simulator,OS=latest,name=Apple TV 4K (2nd generation)';; - 'watchOS') DESTINATION='platform=watchOS Simulator,OS=latest,name=Apple Watch Series 7 (45mm)';; - *) echo "::error title=Unknown platform!::Unknown platform: ${PLATFORM}" && exit 1;; - esac - echo "xcode=${DESTINATION}" >> "${GITHUB_OUTPUT}" - - uses: actions/checkout@v4 - # PIF ISSUES: https://github.com/apple/swift-package-manager/issues/5767 - - uses: actions/cache@v3 - with: - path: .build - key: ${{ runner.os }}-${{ steps.os-version.outputs.version }}-spm-${{ steps.swift-version.outputs.version }}-${{ hashFiles('**/Package.resolved') }} - restore-keys: | - ${{ runner.os }}-${{ steps.os-version.outputs.version }}-spm-${{ steps.swift-version.outputs.version }}- - - name: Work around PIF issues - env: - DESTINATION: ${{ steps.destination.outputs.xcode }} - run: | - swift package dump-pif > /dev/null - ATTEMPT=0 - while [ -z "${SUCCESS}" ] && [ "${ATTEMPT}" -le 5 ]; do - xcodebuild clean -scheme "${XCODE_SCHEME}" -destination "${DESTINATION}" | grep -q "CLEAN SUCCEEDED" && SUCCESS=true - ATTEMPT=$((ATTEMPT + 1)) - done - # END PIF ISSUES - - uses: actions/cache@v3 - with: - path: .derived-data - key: ${{ runner.os }}-${{ steps.os-version.outputs.version }}-xcode-${{ steps.swift-version.outputs.version }}-${{ matrix.platform }}-${{ hashFiles('**/Package.resolved') }} - restore-keys: | - ${{ runner.os }}-${{ steps.os-version.outputs.version }}-xcode-${{ steps.swift-version.outputs.version }}-${{ matrix.platform }}- - - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - spm-package: './' - scheme: ${{ env.XCODE_SCHEME }} - destination: ${{ steps.destination.outputs.xcode }} - action: test - parallel-testing-enabled: ${{ matrix.platform != 'watchOS' }} - enable-code-coverage: true - derived-data-path: .derived-data - - uses: sersoft-gmbh/swift-coverage-action@v4 - id: coverage-files - with: - search-paths: | - ./.build - ./.derived-data - $HOME/Library/Developer/Xcode/DerivedData - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }} - fail_ci_if_error: true + swift-version-offset: [ 0 ] + uses: sersoft-gmbh/oss-common-actions/.github/workflows/swift-test-xcode.yml@main + with: + xcode-scheme: ${{ needs.variables.outputs.xcode-scheme }} + max-swift-version: ${{ needs.variables.outputs.max-supported-swift-version }} + swift-version-offset: ${{ matrix.swift-version-offset }} + platform: ${{ matrix.platform }} + platform-version: ${{ needs.variables.outputs.xcode-platform-version }} + fail-if-codecov-fails: ${{ fromJson(needs.variables.outputs.fail-if-codecov-fails) }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index f3617e8..13bc1d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,23 @@ -# macOS .DS_Store -# Swift Package Manager /.build -# /.swiftpm /Packages -/*.xcodeproj - -# IDEs xcuserdata/ -.idea/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc + +# VS Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..e65252d --- /dev/null +++ b/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index b3f27a6..571f839 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,17 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription -import Foundation + +let swiftSettings: Array = [ + .enableUpcomingFeature("ConciseMagicFile"), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("BareSlashRegexLiterals"), + .enableUpcomingFeature("DisableOutwardActorInference"), +// .enableExperimentalFeature("AccessLevelOnImport"), +// .enableExperimentalFeature("VariadicGenerics"), +// .unsafeFlags(["-warn-concurrency"], .when(configuration: .debug)), +] let package = Package( name: "GCDOperations", @@ -21,23 +30,26 @@ let package = Package( name: "GCDOperations", targets: ["GCDOperations"]), ], + dependencies: [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( - name: "GCDCoreOperations"), + name: "GCDCoreOperations", + swiftSettings: swiftSettings), .target( name: "GCDOperations", - dependencies: ["GCDCoreOperations"]), + dependencies: ["GCDCoreOperations"], + swiftSettings: swiftSettings), .testTarget( name: "GCDCoreOperationsTests", - dependencies: ["GCDCoreOperations"]), + dependencies: ["GCDCoreOperations"], + swiftSettings: swiftSettings), .testTarget( name: "GCDOperationsTests", - dependencies: ["GCDOperations"]), + dependencies: ["GCDOperations"], + swiftSettings: swiftSettings), ] ) - -if ProcessInfo.processInfo.environment["ENABLE_DOCC_SUPPORT"] == "1" { - package.dependencies.append(.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0")) -} diff --git a/Package@swift-5.6.swift b/Package@swift-5.6.swift deleted file mode 100644 index b3fb076..0000000 --- a/Package@swift-5.6.swift +++ /dev/null @@ -1,43 +0,0 @@ -// swift-tools-version:5.6 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription -import Foundation - -let package = Package( - name: "GCDOperations", - platforms: [ - .macOS(.v10_14), - .iOS(.v12), - .tvOS(.v12), - .watchOS(.v4), - ], - products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .library( - name: "GCDCoreOperations", - targets: ["GCDCoreOperations"]), - .library( - name: "GCDOperations", - targets: ["GCDOperations"]), - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. - .target( - name: "GCDCoreOperations"), - .target( - name: "GCDOperations", - dependencies: ["GCDCoreOperations"]), - .testTarget( - name: "GCDCoreOperationsTests", - dependencies: ["GCDCoreOperations"]), - .testTarget( - name: "GCDOperationsTests", - dependencies: ["GCDOperations"]), - ] -) - -if ProcessInfo.processInfo.environment["ENABLE_DOCC_SUPPORT"] == "1" { - package.dependencies.append(.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0")) -} diff --git a/README.md b/README.md index 2c0b2d4..d1880b6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Tests](https://github.com/ffried/GCDOperations/workflows/Tests/badge.svg) [![Docs](https://img.shields.io/badge/-documentation-informational)](https://ffried.github.io/GCDOperations) - + Operations written in Swift based purely on GCD - no Objective-C dynamics, no key value observing. @@ -17,8 +17,8 @@ On top of that, this library embraces what Apple initially showed in in the ["Ad You can find the online documentation of this project here: -- [GCDCoreOperations](https://ffried.github.io/GCDOperations/master/documentation/gcdcoreoperations) -- [GCDOperations](https://ffried.github.io/GCDOperations/master/documentation/gcdoperations). +- [GCDCoreOperations](https://ffried.github.io/GCDOperations/main/documentation/gcdcoreoperations) +- [GCDOperations](https://ffried.github.io/GCDOperations/main/documentation/gcdoperations). ## Credits diff --git a/Sources/GCDCoreOperations/Base Classes/BlockOperation.swift b/Sources/GCDCoreOperations/Base Classes/BlockOperation.swift index 1f89607..70677d1 100644 --- a/Sources/GCDCoreOperations/Base Classes/BlockOperation.swift +++ b/Sources/GCDCoreOperations/Base Classes/BlockOperation.swift @@ -1,11 +1,11 @@ /// A simple operation that runs a given block. /// This operation can either execute a synchronous block and finish immediately afterwards, -/// or run an an asynchrounous block that is passed a reference to the `Operation.finish(with:)` method. +/// or run an an asynchrounous block that is passed a reference to the ``Operation/finish(with:)`` method. public final class BlockOperation: Operation { /// A block that runs synchronously. public typealias SyncBlock = () throws -> () - /// A block that runs asynchronously. `finish` is the reference to the `Operation.finish(with:)` method of the operation. - public typealias AsyncBlock = (_ finish: @escaping @Sendable (Array) -> ()) -> () + /// A block that runs asynchronously. `finish` is the reference to the ``Operation/finish(with:)`` method of the operation. + public typealias AsyncBlock = (_ finish: @escaping @Sendable (Array) -> ()) -> () private enum ExecutionMode { case sync(SyncBlock) @@ -26,7 +26,6 @@ public final class BlockOperation: Operation { executionMode = .async(asyncBlock) } - /// inherited public override func execute() { switch executionMode { case .sync(let block): diff --git a/Sources/GCDCoreOperations/Base Classes/GroupOperation.swift b/Sources/GCDCoreOperations/Base Classes/GroupOperation.swift index 36ec4b9..2101f3d 100644 --- a/Sources/GCDCoreOperations/Base Classes/GroupOperation.swift +++ b/Sources/GCDCoreOperations/Base Classes/GroupOperation.swift @@ -43,9 +43,7 @@ public final class GroupOperation: Operation { /// Adds a collection of operations to this GroupOperation. /// - Parameter ops: The operations to add. /// - Precondition: The GroupOperation must not have finished yet! - public func addOperations(_ ops: Operations) - where Operations: Collection, Operations.Element == Operation - { + public func addOperations(_ ops: some Collection) { assert(!isFinished, "Cannot add operations after GroupOperation has finished!") __operations.withValue { $0.append(contentsOf: ops) } if case .running = state, let queue = queue { @@ -56,7 +54,7 @@ public final class GroupOperation: Operation { /// Adds a variadic list of operations to this GroupOperation. /// - Parameter ops: The variadic list of operations to add. /// - Precondition: The GroupOperation must not have finished yet! - /// - SeeAlso: `GroupOperation.addOperations(_:)` + /// - SeeAlso: ``GroupOperation/addOperations(_:)`` @inlinable public func addOperations(_ ops: Operation...) { addOperations(ops) @@ -67,7 +65,6 @@ public final class GroupOperation: Operation { op.enqueue(on: queue, in: group) } - /// inherited public override func execute() { guard let queue = queue else { return finish() } operations.forEach { includeOperation($0, on: queue) } diff --git a/Sources/GCDCoreOperations/Base Classes/Operation.swift b/Sources/GCDCoreOperations/Base Classes/Operation.swift index f36b147..9ce1146 100644 --- a/Sources/GCDCoreOperations/Base Classes/Operation.swift +++ b/Sources/GCDCoreOperations/Base Classes/Operation.swift @@ -26,10 +26,10 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un internal private(set) final var queue: DispatchQueue? /// The list of observers of this operation. - public private(set) final var observers = Array() + public private(set) final var observers = Array() /// The list of conditions of this operation. - public private(set) final var conditions = Array() - + public private(set) final var conditions = Array() + @Synchronized private final var _dependencies = ContiguousArray() /// The list of dependencies of this operation. @@ -41,9 +41,9 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un /// The list of errors this operation encountered. @Synchronized - private final var _errors = Array() - public final var errors: Array { _errors } - + private final var _errors = Array() + public final var errors: Array { _errors } + // MARK: - State Accessors /// Whether or not this operation was cancelled. public final var isCancelled: Bool { state.isCancelled } @@ -119,23 +119,23 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un // MARK: - Errors /// Aggregates a collection of errors into the list of errors of this operation. /// - Parameter newErrors: The collections of errors to aggregate. - public final func aggregate(errors newErrors: Errors) where Errors: Collection, Errors.Element: Error { + public final func aggregate(errors newErrors: some Collection) { __errors.withValue { $0.append(contentsOf: newErrors.lazy.map { $0 }) } } /// Aggregates a variadic list of errors into the list of errors of this operation. /// - Parameter newErrors: The variadic list of errors to aggregate. - /// - SeeAlso: `aggregate(errors:)` + /// - SeeAlso: ``Operation/aggregate(errors:)`` @inlinable - public final func aggregate(errors: Error...) { + public final func aggregate(errors: any Error...) { aggregate(errors: errors) } /// Aggregates an error into the list of errors of this operation. /// - Parameter error: The error to aggregate. - /// - SeeAlso: `aggregate(errors:)` + /// - SeeAlso: ``Operation/aggregate(errors:)`` @inlinable - public final func aggregate(error: Error) { + public final func aggregate(error: some Error) { aggregate(errors: CollectionOfOne(error)) } @@ -209,7 +209,7 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un let conditionGroup = DispatchGroup() - var results = [OperationConditionResult?](repeating: nil, count: conditions.count) + var results = Array(repeating: nil, count: conditions.count) for (index, condition) in conditions.enumerated() { conditionGroup.enter() condition.evaluate(for: self) { result in @@ -226,7 +226,7 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un self.aggregate(error: AnyConditionFailed()) } - let failures: [Error] = results.compactMap { $0?.error } + let failures: Array = results.compactMap { $0?.error } if !failures.isEmpty { // TODO: If operation was cancelled by condition, this won't do anything! self.finish(with: failures) @@ -252,9 +252,7 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un _waiters.withValue { $0.removeAll() } } - private final func finish(cancelled: Bool, errors errs: Errors) - where Errors: Collection, Errors.Element: Error - { + private final func finish(cancelled: Bool, errors errs: some Collection) { assert(cancelled || state > .enqueued, "Finishing Operation that was never enqueued!") guard !state.isFinished else { return } let (isAlreadyFinishing, wasWaitingForDependencies): (Bool, Bool) = _state.withValue { state in @@ -289,7 +287,7 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un /// Finishes the operation with a list of errors (can be empty). /// - Parameter errors: The errors to finish with. Can be an empty collection. - public final func finish(with errors: Errors) where Errors: Collection, Errors.Element: Error { + public final func finish(with errors: some Collection) { finish(cancelled: false, errors: errors) } @@ -297,20 +295,20 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un /// - Parameter errors: The variadic list of errors to finish with. /// - SeeAlso: `finish(with:)` @inlinable - public final func finish(with errors: Error...) { + public final func finish(with errors: any Error...) { finish(with: errors) } /// Cancels the operation with a list of errors (can be empty). /// - Parameter errors: The errors to cancel with. Can be an empty collection. - public final func cancel(with errors: Errors) where Errors: Collection, Errors.Element: Error { + public final func cancel(with errors: some Collection) { finish(cancelled: true, errors: errors) } /// Cancels the operation with a variadic list of errors (can be empty). /// - Parameter errors: The variadic list of errors to cancel with. @inlinable - public final func cancel(with errors: Error...) { + public final func cancel(with errors: any Error...) { cancel(with: errors) } @@ -318,12 +316,12 @@ open class Operation: CustomStringConvertible, CustomDebugStringConvertible, @un /// - Parameters: /// - wasCancelled: Whether or not the operation was cancelled. The value is the same as `isCancelled`. It's passed to this method to prevent the locks that need to be taken for `isCancelled` to be retrieved. /// - errors: The list of errors that the operation has aggregated. The value is the same as the `errors` property. It is passed to this method to prevent the locks that need to be taken for `errors` to be retrieved. - open func didFinish(wasCancelled: Bool, errors: Array) {} + open func didFinish(wasCancelled: Bool, errors: Array) {} } // MARK: - Nested Types extension Operation { - enum State: Comparable, CustomStringConvertible { + enum State: Sendable, Comparable, CustomStringConvertible { case created case enqueued case waitingForDependencies @@ -386,8 +384,17 @@ extension Operation { } } -fileprivate struct AnyConditionFailed: AnyConditionError { - var conditionName: String { "AnyCondition" } +fileprivate struct AnyConditionFailed: ConditionError { + struct Condition: OperationCondition { + static let name = "AnyCondition" + static var isMutuallyExclusive: Bool { false } + + func dependency(for operation: Operation) -> Operation? { nil } + + func evaluate(for operation: Operation, completion: @escaping (OperationConditionResult) -> ()) { + completion(.failed(AnyConditionFailed())) + } + } } fileprivate extension Synchronized where Value == Operation.State { diff --git a/Sources/GCDCoreOperations/Base Classes/OperationQueue.swift b/Sources/GCDCoreOperations/Base Classes/OperationQueue.swift index e2d677b..4094a60 100644 --- a/Sources/GCDCoreOperations/Base Classes/OperationQueue.swift +++ b/Sources/GCDCoreOperations/Base Classes/OperationQueue.swift @@ -36,7 +36,7 @@ public final class OperationQueue: @unchecked Sendable { queue.addOperation(newOperation) } - func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: [Error]) { + func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) { queue.operationFinished(operation) queue = nil // Op has finished. Release the queue. } diff --git a/Sources/GCDCoreOperations/Condition/OperationCondition.swift b/Sources/GCDCoreOperations/Condition/OperationCondition.swift index fa66616..24e5f80 100644 --- a/Sources/GCDCoreOperations/Condition/OperationCondition.swift +++ b/Sources/GCDCoreOperations/Condition/OperationCondition.swift @@ -26,46 +26,31 @@ public protocol OperationCondition: Sendable { func evaluate(for operation: Operation, completion: @escaping (OperationConditionResult) -> ()) } -/// An error representing a failed condition. This protocol is an implementation detail and should not be used directly. Use `ConditionError` instead. -public protocol AnyConditionError: Error { - /// The name of the condition that failed. - var conditionName: String { get } -} - -#if compiler(>=5.7) -/// An error describing a failed condition. -public protocol ConditionError: AnyConditionError { - /// The condition that has failed. - associatedtype Condition: OperationCondition -} -#else /// An error describing a failed condition. -public protocol ConditionError: AnyConditionError { +public protocol ConditionError: Error { /// The condition that has failed. associatedtype Condition: OperationCondition } -#endif extension ConditionError { - /// inherited @inlinable public var conditionName: String { Condition.name } } -/// An enum to indicate whether an `OperationCondition` was satisfied, or if it -/// failed with an error. +@available(*, deprecated, message: "Use any ConditionError instead") +public typealias AnyConditionError = any ConditionError + +/// An enum to indicate whether an ``OperationCondition`` was satisfied, or if it failed with an error. public enum OperationConditionResult: Sendable { /// The condition was satisified, continue execution. case satisfied - /// The condition failed, abort execution. The associated `ConditionError` describes what failure happened during evaluation. - case failed(AnyConditionError) - - var error: AnyConditionError? { + /// The condition failed, abort execution. The associated ``ConditionError`` describes what failure happened during evaluation. + case failed(any ConditionError) + + var error: (any ConditionError)? { switch self { - case .failed(let error): - return error - default: - return nil + case .satisfied: nil + case .failed(let error): error } } } diff --git a/Sources/GCDCoreOperations/Helpers/Synchronized.swift b/Sources/GCDCoreOperations/Helpers/Synchronized.swift index 07ad228..139685e 100644 --- a/Sources/GCDCoreOperations/Helpers/Synchronized.swift +++ b/Sources/GCDCoreOperations/Helpers/Synchronized.swift @@ -27,7 +27,8 @@ final class Synchronized: @unchecked Sendable { } } - func coordinated(with other: Synchronized, do work: (inout Value, inout OtherValue) throws -> T) rethrows -> T { + func coordinated(with other: Synchronized, + do work: (inout Value, inout OtherValue) throws -> T) rethrows -> T { dispatchPrecondition(condition: .notOnQueue(accessQueue)) return try accessQueue.sync(flags: .barrier) { if other.accessQueue === accessQueue { // unlikely diff --git a/Sources/GCDCoreOperations/Observer/BlockObserver.swift b/Sources/GCDCoreOperations/Observer/BlockObserver.swift index 667594e..79f7ac2 100644 --- a/Sources/GCDCoreOperations/Observer/BlockObserver.swift +++ b/Sources/GCDCoreOperations/Observer/BlockObserver.swift @@ -1,13 +1,13 @@ -/// A simple `OperationObserver` implementation that executes blocks for each of the methods. +/// A simple ``OperationObserver`` implementation that executes blocks for each of the methods. public struct BlockObserver: OperationObserver { private let startHandler: ((Operation) -> Void)? private let produceHandler: ((Operation, Operation) -> Void)? - private let finishHandler: ((Operation, Bool, Array) -> Void)? + private let finishHandler: ((Operation, Bool, Array) -> Void)? @usableFromInline init(_startHandler: ((Operation) -> Void)?, _produceHandler: ((Operation, Operation) -> Void)?, - _finishHandler: ((Operation, Bool, Array) -> Void)?) { + _finishHandler: ((Operation, Bool, Array) -> Void)?) { startHandler = _startHandler produceHandler = _produceHandler finishHandler = _finishHandler @@ -21,7 +21,7 @@ public struct BlockObserver: OperationObserver { @inlinable public init(startHandler: ((Operation) -> Void)? = nil, produceHandler: ((Operation, Operation) -> Void)? = nil, - finishHandler: ((Operation, Bool, Array) -> Void)?) { + finishHandler: ((Operation, Bool, Array) -> Void)?) { self.init(_startHandler: startHandler, _produceHandler: produceHandler, _finishHandler: finishHandler) @@ -48,18 +48,15 @@ public struct BlockObserver: OperationObserver { _finishHandler: nil) } - /// inherited public func operationDidStart(_ operation: Operation) { startHandler?(operation) } - /// inherited public func operation(_ operation: Operation, didProduce newOperation: Operation) { produceHandler?(operation, newOperation) } - /// inherited - public func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) { + public func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) { finishHandler?(operation, cancelled, errors) } } diff --git a/Sources/GCDCoreOperations/Observer/OperationObserver.swift b/Sources/GCDCoreOperations/Observer/OperationObserver.swift index acd73e4..31cabf2 100644 --- a/Sources/GCDCoreOperations/Observer/OperationObserver.swift +++ b/Sources/GCDCoreOperations/Observer/OperationObserver.swift @@ -1,18 +1,18 @@ /// Describes a type that observers operations. @preconcurrency public protocol OperationObserver: Sendable { - /// Invoked immediately prior to the `Operation.execute()` method. + /// Invoked immediately prior to the ``Operation/execute()`` method. func operationDidStart(_ operation: Operation) - /// Invoked when `Operation.produce(_:)` is executed. + /// Invoked when ``Operation/produce(_:)`` is executed. func operation(_ operation: Operation, didProduce newOperation: Operation) - /// Invoked when an `Operation` finishes, along with whether it was cancelled and any errors produced during execution. - func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) + /// Invoked when an ``Operation`` finishes, along with whether it was cancelled and any errors produced during execution. + func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) } // MARK: - Helper Extension -extension Sequence where Iterator.Element == OperationObserver { +extension Sequence where Iterator.Element == any OperationObserver { @inlinable func operationDidStart(_ operation: Operation) { forEach { $0.operationDidStart(operation) } @@ -24,7 +24,7 @@ extension Sequence where Iterator.Element == OperationObserver { } @inlinable - func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) { + func operationDidFinish(_ operation: Operation, wasCancelled cancelled: Bool, errors: Array) { forEach { $0.operationDidFinish(operation, wasCancelled: cancelled, errors: errors) } } } diff --git a/Sources/GCDOperations/Conditions/MutuallyExclusive.swift b/Sources/GCDOperations/Conditions/MutuallyExclusive.swift index 25c3e99..ff9d397 100644 --- a/Sources/GCDOperations/Conditions/MutuallyExclusive.swift +++ b/Sources/GCDOperations/Conditions/MutuallyExclusive.swift @@ -3,18 +3,14 @@ import protocol GCDCoreOperations.OperationCondition /// A generic condition for describing kinds of operations that may not execute concurrently. public struct MutuallyExclusive: OperationCondition { - /// inherited public static var name: String { "MutuallyExclusive<\(T.self)>" } - /// inherited public static var isMutuallyExclusive: Bool { true } /// Creates a new MutuallyExclusive condition. public init() {} - /// inherited public func dependency(for operation: GCDCoreOperations.Operation) -> GCDCoreOperations.Operation? { nil } - /// inherited public func evaluate(for operation: GCDCoreOperations.Operation, completion: @escaping (OperationConditionResult) -> ()) { completion(.satisfied) } diff --git a/Sources/GCDOperations/Conditions/Negated.swift b/Sources/GCDOperations/Conditions/Negated.swift index 69fd3f7..e62f41a 100644 --- a/Sources/GCDOperations/Conditions/Negated.swift +++ b/Sources/GCDOperations/Conditions/Negated.swift @@ -9,16 +9,13 @@ import protocol GCDCoreOperations.OperationCondition public struct NegatedCondition: OperationCondition { /// The error produced, when the negated condition succeeded. public struct Error: ConditionError { - /// inherited public typealias Condition = NegatedCondition /// The condition that was negated but succeeded. public let negatedCondition: ConditionToNegate } - /// inherited public static var name: String { "Not<\(ConditionToNegate.name)>" } - /// inherited public static var isMutuallyExclusive: Bool { ConditionToNegate.isMutuallyExclusive } private let condition: ConditionToNegate @@ -29,12 +26,10 @@ public struct NegatedCondition: Operation self.condition = condition } - /// inherited public func dependency(for operation: GCDOperation) -> GCDOperation? { condition.dependency(for: operation) } - /// inherited public func evaluate(for operation: GCDOperation, completion: @escaping (OperationConditionResult) -> ()) { condition.evaluate(for: operation) { switch $0 { diff --git a/Sources/GCDOperations/Conditions/NoCancelledDependencies.swift b/Sources/GCDOperations/Conditions/NoCancelledDependencies.swift index 558e857..38b7070 100644 --- a/Sources/GCDOperations/Conditions/NoCancelledDependencies.swift +++ b/Sources/GCDOperations/Conditions/NoCancelledDependencies.swift @@ -8,25 +8,20 @@ import protocol GCDCoreOperations.OperationCondition public struct NoCancelledDependencies: OperationCondition { /// The error produced when any of the operation's dependencies was cancelled. public struct Error: ConditionError { - /// inherited public typealias Condition = NoCancelledDependencies /// The dependencies that were cancelled. public let cancelledDependencies: ContiguousArray } - /// inherited public static let name = "NoCancelledDependencies" - /// inherited. public static let isMutuallyExclusive = false /// Creates a new NoCancelledDependencies condition. public init() {} - /// inherited public func dependency(for operation: GCDOperation) -> GCDOperation? { nil } - /// inherited public func evaluate(for operation: GCDOperation, completion: @escaping (OperationConditionResult) -> ()) { // Verify that all of the dependencies executed. let cancelled = ContiguousArray(operation.dependencies.lazy.filter(\.isCancelled)) diff --git a/Sources/GCDOperations/Conditions/NoFailedDependencies.swift b/Sources/GCDOperations/Conditions/NoFailedDependencies.swift index e1b370d..a42c154 100644 --- a/Sources/GCDOperations/Conditions/NoFailedDependencies.swift +++ b/Sources/GCDOperations/Conditions/NoFailedDependencies.swift @@ -8,25 +8,20 @@ import protocol GCDCoreOperations.OperationCondition public struct NoFailedDependencies: OperationCondition { /// The error produced when any of the operation's dependencies have failed. public struct Error: ConditionError { - /// inherited public typealias Condition = NoFailedDependencies /// The dependencies that have failed. public let failedDependencies: ContiguousArray } - /// inherited public static let name = "NoFailedDependencies" - /// inhertied public static let isMutuallyExclusive = false /// Creates a new NoFailedDependencies condition. public init() {} - /// inherited public func dependency(for operation: GCDOperation) -> GCDOperation? { nil } - /// inherited public func evaluate(for operation: GCDOperation, completion: @escaping (OperationConditionResult) -> ()) { // Verify that all of the dependencies executed without errors. let failed = ContiguousArray(operation.dependencies.filter { !$0.errors.isEmpty }) diff --git a/Sources/GCDOperations/Conditions/Silent.swift b/Sources/GCDOperations/Conditions/Silent.swift index 99eca71..000ca5e 100644 --- a/Sources/GCDOperations/Conditions/Silent.swift +++ b/Sources/GCDOperations/Conditions/Silent.swift @@ -8,12 +8,10 @@ import protocol GCDCoreOperations.OperationCondition do not already have it. */ public struct SilentCondition: OperationCondition { - /// inherited public static var name: String { "Silent<\(Condition.name)>" } - /// inherited public static var isMutuallyExclusive: Bool { Condition.isMutuallyExclusive } - + private let condition: Condition /// Creates a new SilentCondition that silences the given condition. @@ -22,13 +20,11 @@ public struct SilentCondition: OperationCondition self.condition = condition } - /// inherited public func dependency(for operation: GCDOperation) -> GCDOperation? { // We never generate a dependency. nil } - /// inherited public func evaluate(for operation: GCDOperation, completion: @escaping (OperationConditionResult) -> ()) { condition.evaluate(for: operation, completion: completion) } diff --git a/Sources/GCDOperations/Observers/Timeout.swift b/Sources/GCDOperations/Observers/Timeout.swift index c7d8f80..5962de9 100644 --- a/Sources/GCDOperations/Observers/Timeout.swift +++ b/Sources/GCDOperations/Observers/Timeout.swift @@ -3,7 +3,7 @@ import typealias Foundation.TimeInterval import typealias GCDCoreOperations.GCDOperation import protocol GCDCoreOperations.OperationObserver -/// `TimeoutObserver` is a way to make an `Operation` automatically time out and cancel after a specified time interval. +/// `TimeoutObserver` is a way to make an ``GCDOperation`` automatically time out and cancel after a specified time interval. public struct TimeoutObserver: OperationObserver { private let timeout: TimeInterval @@ -13,7 +13,6 @@ public struct TimeoutObserver: OperationObserver { self.timeout = timeout } - /// inherited public func operationDidStart(_ operation: GCDOperation) { // When the operation starts, queue up a block to cause it to time out. DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { [timeout] in @@ -24,10 +23,8 @@ public struct TimeoutObserver: OperationObserver { } } - /// inherited public func operation(_ operation: GCDOperation, didProduce newOperation: GCDOperation) {} - /// inherited - public func operationDidFinish(_ operation: GCDOperation, wasCancelled cancelled: Bool, errors: [Error]) {} + public func operationDidFinish(_ operation: GCDOperation, wasCancelled cancelled: Bool, errors: Array) {} } extension TimeoutObserver { diff --git a/Sources/GCDOperations/Operations/DelayOperation.swift b/Sources/GCDOperations/Operations/DelayOperation.swift index 8638fbc..b9c1556 100644 --- a/Sources/GCDOperations/Operations/DelayOperation.swift +++ b/Sources/GCDOperations/Operations/DelayOperation.swift @@ -4,7 +4,7 @@ import typealias Foundation.TimeInterval import typealias GCDCoreOperations.GCDOperation /** - `DelayOperation` is an `Operation` that will simply wait for a given time + `DelayOperation` is an ``Operation`` that will simply wait for a given time interval, or until a specific `Date`. It is important to note that this operation does **not** use the `sleep()` @@ -44,7 +44,6 @@ public final class DelayOperation: GCDOperation { super.init() } - /// inherited public override func execute() { let interval = delay.interval guard interval > 0 else {