From b2506d82036f51e75cbf1e5fdb4ae8e533a4e6f3 Mon Sep 17 00:00:00 2001 From: Florian Friedrich Date: Mon, 2 Oct 2023 18:32:22 +0200 Subject: [PATCH] Add support for Swift 5.9 --- 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 | 23 +- .../xcschemes/FFFoundation.xcscheme | 91 +++++++ Package.resolved | 23 ++ Package.swift | 51 ++-- Package@swift-5.6.swift | 29 --- Sources/FFFoundation/CacheManager.swift | 72 +++--- Sources/FFFoundation/Containers/CoW.swift | 4 +- Sources/FFFoundation/Containers/Lazy.swift | 4 +- Sources/FFFoundation/Containers/Ref.swift | 4 +- .../Containers/Synchronized.swift | 4 +- Sources/FFFoundation/Containers/Weak.swift | 4 +- Sources/FFFoundation/Diff.swift | 8 +- Sources/FFFoundation/GCDFuture.swift | 20 +- Sources/FFFoundation/Geometry/Angle.swift | 4 +- .../TypeDescriptionTests.swift | 22 +- 20 files changed, 308 insertions(+), 487 deletions(-) rename CODEOWNERS => .github/CODEOWNERS (71%) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/FFFoundation.xcscheme 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 706801d4..5d6c72cf 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 d85552df..0bf5a9d4 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 494b3506..485cc107 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: macos-12 - 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: macOS + 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 6b3a13af..74fbf988 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 14532738..53c7c234 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: FFFoundation + 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: FFFoundation - 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 d98efc24..13bc1d56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,23 @@ .DS_Store + /.build -/.swiftpm /Packages -/*.xcodeproj -*.xcuserdatad +xcuserdata/ +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/.swiftpm/xcode/xcshareddata/xcschemes/FFFoundation.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/FFFoundation.xcscheme new file mode 100644 index 00000000..7d7743b0 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/FFFoundation.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..e65252d0 --- /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 fc3528a2..53f55a57 100644 --- a/Package.swift +++ b/Package.swift @@ -1,30 +1,41 @@ -// 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: "FFFoundation", platforms: [ - .macOS(.v10_13), - .iOS(.v11), - .tvOS(.v11), - .watchOS(.v4), + .macOS(.v10_13), + .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: "FFFoundation", - targets: ["FFFoundation"]), - ], - targets: [ - .target(name: "FFFoundation"), - .testTarget( - name: "FFFoundationTests", - dependencies: ["FFFoundation"]), - ] + // Products define the executables and libraries produced by a package, and make them visible to other packages. + .library(name: "FFFoundation", + targets: ["FFFoundation"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), + ], + targets: [ + .target( + name: "FFFoundation", + swiftSettings: swiftSettings), + .testTarget( + name: "FFFoundationTests", + dependencies: ["FFFoundation"], + 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 bdc73354..00000000 --- a/Package@swift-5.6.swift +++ /dev/null @@ -1,29 +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: "FFFoundation", - platforms: [ - .macOS(.v10_12), - .iOS(.v10), - .tvOS(.v10), - .watchOS(.v3), - ], - products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .library(name: "FFFoundation", targets: ["FFFoundation"]), - ], - targets: [ - .target(name: "FFFoundation"), - .testTarget( - name: "FFFoundationTests", - dependencies: ["FFFoundation"]), - ] -) - -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/Sources/FFFoundation/CacheManager.swift b/Sources/FFFoundation/CacheManager.swift index 062995a8..15a4fb7d 100644 --- a/Sources/FFFoundation/CacheManager.swift +++ b/Sources/FFFoundation/CacheManager.swift @@ -56,7 +56,7 @@ public final class CacheManager { } @Synchronized - private var memoryCache: [ObjectIdentification: Object] = [:] + private var memoryCache: Dictionary = .init() public init(name: Name = .default, shouldMigrateFromOldNamingBehavior: Bool = true) throws { self.name = name @@ -65,8 +65,8 @@ public final class CacheManager { let baseFolder = try CacheManager.cacheFolder(in: fileManager) let folder = baseFolder.appendingPathComponent(name.rawValue).appendingPathComponent("\(Object.self)") if shouldMigrateFromOldNamingBehavior, - case let oldPath = baseFolder.appendingPathComponent(name.rawValue + "\(Object.self)"), - fileManager.directoryExists(at: oldPath) { + case let oldPath = baseFolder.appendingPathComponent(name.rawValue + "\(Object.self)"), + fileManager.directoryExists(at: oldPath) { try fileManager.moveItem(at: oldPath, to: folder) } try fileManager.createDirectoryIfNeeded(at: folder) @@ -75,16 +75,16 @@ public final class CacheManager { } // MARK: - Memory warnings - private var memoryWarningsObserver: NSObjectProtocol? + private var memoryWarningsObserver: (any NSObjectProtocol)? private func registerForMemoryWarnings() { - #if canImport(UIKit) && !os(watchOS) - let opQueue = OperationQueue() - opQueue.underlyingQueue = queue - let name = UIApplication.didReceiveMemoryWarningNotification - memoryWarningsObserver = NotificationCenter.default.addObserver(forName: name, object: nil, queue: opQueue) { [weak self] _ in - self?.clearMemoryCache() - } - #endif +#if canImport(UIKit) && !os(watchOS) + let opQueue = OperationQueue() + opQueue.underlyingQueue = queue + let name = UIApplication.didReceiveMemoryWarningNotification + memoryWarningsObserver = NotificationCenter.default.addObserver(forName: name, object: nil, queue: opQueue) { [weak self] _ in + self?.clearMemoryCache() + } +#endif } // MARK: - Private helpers @@ -161,17 +161,21 @@ extension CacheManager { } extension CacheManager { + @frozen public struct Name: RawRepresentable { public typealias RawValue = String public let rawValue: RawValue - public init(rawValue: RawValue) { self.rawValue = rawValue } + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } } } public enum CachingError: Error, CustomStringConvertible { - case couldNotSerialize(underlyingError: Error?) - case couldNotDeserialize(underlyingError: Error?) + case couldNotSerialize(underlyingError: (any Error)?) + case couldNotDeserialize(underlyingError: (any Error)?) public var description: String { switch self { @@ -198,30 +202,30 @@ extension String: Cachable { } #if canImport(UIKit) - extension UIImage: Cachable { - public func cacheData() throws -> Data { - guard let data = jpegData(compressionQuality: 0.95) else { throw CachingError.couldNotSerialize(underlyingError: nil) } - return data - } +extension UIImage: Cachable { + public func cacheData() throws -> Data { + guard let data = jpegData(compressionQuality: 0.95) else { throw CachingError.couldNotSerialize(underlyingError: nil) } + return data + } - public static func fromCache(data: Data) throws -> Self { - guard let img = self.init(data: data) else { throw CachingError.couldNotDeserialize(underlyingError: nil) } - return img - } + public static func fromCache(data: Data) throws -> Self { + guard let img = self.init(data: data) else { throw CachingError.couldNotDeserialize(underlyingError: nil) } + return img } +} #endif #if canImport(AppKit) && !os(iOS) // macCatalyst has os(iOS) - import AppKit - extension NSImage: Cachable { - public func cacheData() throws -> Data { - guard let data = tiffRepresentation else { throw CachingError.couldNotSerialize(underlyingError: nil) } - return data - } +import AppKit +extension NSImage: Cachable { + public func cacheData() throws -> Data { + guard let data = tiffRepresentation else { throw CachingError.couldNotSerialize(underlyingError: nil) } + return data + } - public static func fromCache(data: Data) throws -> Self { - guard let img = self.init(data: data) else { throw CachingError.couldNotDeserialize(underlyingError: nil) } - return img - } + public static func fromCache(data: Data) throws -> Self { + guard let img = self.init(data: data) else { throw CachingError.couldNotDeserialize(underlyingError: nil) } + return img } +} #endif diff --git a/Sources/FFFoundation/Containers/CoW.swift b/Sources/FFFoundation/Containers/CoW.swift index f162abf1..2b6cd287 100644 --- a/Sources/FFFoundation/Containers/CoW.swift +++ b/Sources/FFFoundation/Containers/CoW.swift @@ -86,13 +86,13 @@ extension CoW: Comparable where Value: Comparable { } extension CoW: Encodable where Value: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { try wrappedValue.encode(to: encoder) } } extension CoW: Decodable where Value: Decodable, Value: Copyable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { try self.init(wrappedValue: Value(from: decoder)) } } diff --git a/Sources/FFFoundation/Containers/Lazy.swift b/Sources/FFFoundation/Containers/Lazy.swift index 20a122f0..40137a73 100644 --- a/Sources/FFFoundation/Containers/Lazy.swift +++ b/Sources/FFFoundation/Containers/Lazy.swift @@ -80,13 +80,13 @@ extension Lazy: Comparable where Deferred: Comparable { } extension Lazy: Encodable where Deferred: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { try wrappedValue.encode(to: encoder) } } extension Lazy: Decodable where Deferred: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let value = try Deferred(from: decoder) self.init(wrappedValue: value) } diff --git a/Sources/FFFoundation/Containers/Ref.swift b/Sources/FFFoundation/Containers/Ref.swift index f269d559..acbc0b11 100644 --- a/Sources/FFFoundation/Containers/Ref.swift +++ b/Sources/FFFoundation/Containers/Ref.swift @@ -62,13 +62,13 @@ extension Ref: Comparable where Referenced: Comparable { } extension Ref: Encodable where Referenced: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { try wrappedValue.encode(to: encoder) } } extension Ref: Decodable where Referenced: Decodable { - public convenience init(from decoder: Decoder) throws { + public convenience init(from decoder: any Decoder) throws { let value = try Referenced(from: decoder) self.init(wrappedValue: value) } diff --git a/Sources/FFFoundation/Containers/Synchronized.swift b/Sources/FFFoundation/Containers/Synchronized.swift index 94a40c9f..978c3b64 100644 --- a/Sources/FFFoundation/Containers/Synchronized.swift +++ b/Sources/FFFoundation/Containers/Synchronized.swift @@ -116,13 +116,13 @@ extension Synchronized: Comparable where Guarded: Comparable { } extension Synchronized: Encodable where Guarded: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { try wrappedValue.encode(to: encoder) } } extension Synchronized: Decodable where Guarded: Decodable { - public convenience init(from decoder: Decoder) throws { + public convenience init(from decoder: any Decoder) throws { try self.init(wrappedValue: Guarded(from: decoder)) } } diff --git a/Sources/FFFoundation/Containers/Weak.swift b/Sources/FFFoundation/Containers/Weak.swift index 77e8ea81..fd04fcee 100644 --- a/Sources/FFFoundation/Containers/Weak.swift +++ b/Sources/FFFoundation/Containers/Weak.swift @@ -79,13 +79,13 @@ extension Weak: Hashable where Object: Hashable { //} extension Weak: Encodable where Object: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { try wrappedValue.encode(to: encoder) } } extension Weak: Decodable where Object: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { try self.init(object: Object(from: decoder)) } } diff --git a/Sources/FFFoundation/Diff.swift b/Sources/FFFoundation/Diff.swift index b2d2ea8c..6904a6d1 100644 --- a/Sources/FFFoundation/Diff.swift +++ b/Sources/FFFoundation/Diff.swift @@ -83,7 +83,7 @@ fileprivate extension Diff { } extension Diff: Encodable where Subject: Encodable, Element: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(base, forKey: .base) try container.encode(head, forKey: .head) @@ -97,7 +97,7 @@ extension Diff: Encodable where Subject: Encodable, Element: Encodable { } extension Diff: Decodable where Subject: Decodable, Element: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) base = try container.decode(Subject.self, forKey: .base) head = try container.decode(Subject.self, forKey: .head) @@ -139,7 +139,7 @@ extension Diff { "\(lineSign)\(element)\n" } - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() switch try container.decode(String.self) { case "unchanged": self = .unchanged @@ -151,7 +151,7 @@ extension Diff { } } - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() switch self { case .unchanged: try container.encode("unchanged") diff --git a/Sources/FFFoundation/GCDFuture.swift b/Sources/FFFoundation/GCDFuture.swift index d79844e0..ef032113 100644 --- a/Sources/FFFoundation/GCDFuture.swift +++ b/Sources/FFFoundation/GCDFuture.swift @@ -101,7 +101,7 @@ public final class GCDFuture: @unchecked Sendable { } @inlinable - public func map(_ transformer: @escaping (Value) throws -> T) -> GCDFutureResult { + public func map(_ transformer: @escaping (Value) throws -> T) -> GCDFutureResult { map { val in Result { try transformer(val) } } } @@ -111,10 +111,10 @@ public final class GCDFuture: @unchecked Sendable { return future } - public func flatMap(_ transformer: @escaping (Value) throws -> GCDFuture) -> GCDFutureResult { + public func flatMap(_ transformer: @escaping (Value) throws -> GCDFuture) -> GCDFutureResult { flatMap { [workerQueue] in do { return try transformer($0).map { .success($0) } } - catch { return GCDFutureResult(queue: workerQueue, value: .failure(error)) } + catch { return GCDFutureResult(queue: workerQueue, value: .failure(error)) } } } @@ -167,7 +167,7 @@ extension GCDFutureResult { @discardableResult @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) public func complete(with task: @escaping @Sendable () async throws -> Success) -> Task - where Value == Result + where Value == Result { Task { do { @@ -202,16 +202,16 @@ extension GCDFutureResult { } @inlinable - public func map(_ transformer: @escaping (Success) throws -> T) -> GCDFutureResult + public func map(_ transformer: @escaping (Success) throws -> T) -> GCDFutureResult where Value == Result { map { val in try transformer(val.get()) } } - public func flatMap(_ transformer: @escaping (Success) throws -> GCDFutureResult) -> GCDFutureResult - where Value == Result + public func flatMap(_ transformer: @escaping (Success) throws -> GCDFutureResult) -> GCDFutureResult + where Value == Result { - let future = GCDFutureResult(queue: workerQueue) + let future = GCDFutureResult(queue: workerQueue) whenDone { do { try transformer($0.get()).cascade(other: future) @@ -245,8 +245,8 @@ extension DispatchQueue { return future } - public final func asFuture(do work: @escaping () throws -> T) -> GCDFutureResult { - let future = GCDFutureResult(queue: self) + public final func asFuture(do work: @escaping () throws -> T) -> GCDFutureResult { + let future = GCDFutureResult(queue: self) self.async { future.complete(with: Result { try work() }) } return future } diff --git a/Sources/FFFoundation/Geometry/Angle.swift b/Sources/FFFoundation/Geometry/Angle.swift index 46a82671..c19679f0 100644 --- a/Sources/FFFoundation/Geometry/Angle.swift +++ b/Sources/FFFoundation/Geometry/Angle.swift @@ -238,7 +238,7 @@ fileprivate extension Angle { extension Angle: Sendable where Value: Sendable {} extension Angle: Encodable where Value: Encodable { - public func encode(to encoder: Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch kind { case .degrees: try container.encode("degrees", forKey: .kind) @@ -249,7 +249,7 @@ extension Angle: Encodable where Value: Encodable { } extension Angle: Decodable where Value: Decodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) switch try container.decode(String.self, forKey: .kind) { case "degrees": diff --git a/Tests/FFFoundationTests/TypeDescriptionTests.swift b/Tests/FFFoundationTests/TypeDescriptionTests.swift index 844f2fde..732ce6ba 100644 --- a/Tests/FFFoundationTests/TypeDescriptionTests.swift +++ b/Tests/FFFoundationTests/TypeDescriptionTests.swift @@ -3,7 +3,7 @@ import XCTest protocol GenericTestType { static var typeName: String { get } - static var genericParams: [GenericTestType.Type] { get } + static var genericParams: Array { get } } fileprivate extension GenericTestType { @@ -11,13 +11,13 @@ fileprivate extension GenericTestType { return typeName.firstIndex(of: ".").map { String(typeName[typeName.index(after: $0)...]) } ?? typeName } - static func fullTypeName(basedOn namePath: (GenericTestType.Type) -> String) -> String { + static func fullTypeName(basedOn namePath: (any GenericTestType.Type) -> String) -> String { return namePath(self) + (genericParams.isEmpty ? "" : "<" + genericParams.map { $0.fullTypeName(basedOn: namePath) }.joined(separator: ", ") + ">") } } private func _XCTAssertEqual(_ desc: TypeDescription, _ testType: TestType.Type, _ message: @autoclosure () -> String, file: StaticString, line: UInt) { - func assertEqual(_ desc: TypeDescription, _ testType: GenericTestType.Type, _ message: @autoclosure () -> String, recursionIndexPath: IndexPath) { + func assertEqual(_ desc: TypeDescription, _ testType: any GenericTestType.Type, _ message: @autoclosure () -> String, recursionIndexPath: IndexPath) { func extendedMessage(for message: @autoclosure () -> String) -> String { return recursionIndexPath.isEmpty ? message() @@ -34,19 +34,13 @@ private func _XCTAssertEqual(_ desc: TypeDescription, assertEqual(desc, testType, message(), recursionIndexPath: []) } -#if swift(>=5.3) func XCTAssertEqual(_ desc: TypeDescription, _ testType: TestType.Type, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { _XCTAssertEqual(desc, testType, message(), file: file, line: line) } -#else -func XCTAssertEqual(_ desc: TypeDescription, _ testType: TestType.Type, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { - _XCTAssertEqual(desc, testType, message(), file: file, line: line) -} -#endif extension String: GenericTestType { static let typeName = "Swift.String" - static let genericParams: [GenericTestType.Type] = [] + static let genericParams: Array = [] } final class TypeDescriptionTests: XCTestCase { @@ -56,19 +50,19 @@ final class TypeDescriptionTests: XCTestCase { struct NonGeneric: GenericTestType { static let typeName = "\(TypeDescriptionTests.typePrefix).NonGeneric" - static let genericParams: [GenericTestType.Type] = [] + static let genericParams: Array = [] } struct OneGeneric: GenericTestType { static var typeName: String { return "\(TypeDescriptionTests.typePrefix).OneGeneric" } - static var genericParams: [GenericTestType.Type] { return [T.self] } + static var genericParams: Array { return [T.self] } } struct TwoGeneric: GenericTestType { static var typeName: String { return "\(TypeDescriptionTests.typePrefix).TwoGeneric" } - static var genericParams: [GenericTestType.Type] { return [T.self, U.self] } + static var genericParams: Array { return [T.self, U.self] } } struct ThreeGeneric: GenericTestType { static var typeName: String { return "\(TypeDescriptionTests.typePrefix).ThreeGeneric" } - static var genericParams: [GenericTestType.Type] { return [T.self, U.self, V.self] } + static var genericParams: Array { return [T.self, U.self, V.self] } } override func setUp() {