diff --git a/CODEOWNERS b/.github/CODEOWNERS similarity index 62% rename from CODEOWNERS rename to .github/CODEOWNERS index 97b6ff6..5d6c72c 100644 --- a/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,12 +2,11 @@ * @ffried # Workflow & Deployment related files -.github/* @ffried -.jazzy.yaml @ffried -.codecov.yml @ffried +.github/* @ffried +.codecov.yml @ffried # Project & Source files *.swift @ffried -/Package.* @ffried +/Package*.swift @ffried /Sources/* @ffried /Tests/* @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 494b350..485cc10 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 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 e521008..82f9a55 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: FFCoreData + 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 ] + 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: FFCoreData - 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 d98efc2..13bc1d5 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/Package.resolved b/Package.resolved index 03cd99c..23c12cf 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ffried/FFFoundation.git", "state" : { - "revision" : "ccc1998e5bfb75d20eb51d120765d334459baba4", - "version" : "9.5.0" + "revision" : "36ae2b757e7235a0d855411cca343282fda526b3", + "version" : "9.6.0" + } + }, + { + "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" } } ], diff --git a/Package.swift b/Package.swift index 87ab0c6..31b6c1a 100644 --- a/Package.swift +++ b/Package.swift @@ -1,16 +1,25 @@ -// 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: "FFCoreData", platforms: [ - .iOS(.v11), .macOS(.v10_13), + .iOS(.v12), + .tvOS(.v12), .watchOS(.v4), - .tvOS(.v11), ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. @@ -19,7 +28,8 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/ffried/FFFoundation.git", from: "9.0.0"), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), + .package(url: "https://github.com/ffried/FFFoundation.git", from: "9.6.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -28,14 +38,12 @@ let package = Package( name: "FFCoreData", dependencies: [ .product(name: "FFFoundation", package: "FFFoundation"), - ]), + ], + swiftSettings: swiftSettings), .testTarget( name: "FFCoreDataTests", dependencies: ["FFCoreData"], - resources: [.process("Models/TestModel.xcdatamodeld")]), + resources: [.process("Models/TestModel.xcdatamodeld")], + 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 d7f294d..0000000 --- a/Package@swift-5.6.swift +++ /dev/null @@ -1,40 +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: "FFCoreData", - platforms: [ - .iOS(.v10), - .macOS(.v10_12), - .watchOS(.v4), - .tvOS(.v10), - ], - products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .library(name: "FFCoreData", targets: ["FFCoreData"]), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/ffried/FFFoundation.git", from: "9.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: "FFCoreData", - dependencies: [ - .product(name: "FFFoundation", package: "FFFoundation"), - ]), - .testTarget( - name: "FFCoreDataTests", - dependencies: ["FFCoreData"], - resources: [.process("Models/TestModel.xcdatamodeld")]), - ] -) - -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/FFCoreData/CoreDataManager/CoreDataManager.swift b/Sources/FFCoreData/CoreDataManager/CoreDataManager.swift index 52ccf80..b5badf8 100644 --- a/Sources/FFCoreData/CoreDataManager/CoreDataManager.swift +++ b/Sources/FFCoreData/CoreDataManager/CoreDataManager.swift @@ -155,7 +155,8 @@ fileprivate final class CoreDataManager { @frozen public enum CoreDataStack { - @Lazy private static var manager = CoreDataManager(configuration: configuration) + @Lazy + private static var manager = CoreDataManager(configuration: configuration) private static var _configuration: Configuration? { didSet { diff --git a/Sources/FFCoreData/ManagedObjectContext Observer/MOCBlockObservers.swift b/Sources/FFCoreData/ManagedObjectContext Observer/MOCBlockObservers.swift index ea13aa0..c5bc43b 100644 --- a/Sources/FFCoreData/ManagedObjectContext Observer/MOCBlockObservers.swift +++ b/Sources/FFCoreData/ManagedObjectContext Observer/MOCBlockObservers.swift @@ -32,7 +32,7 @@ public final class MOCBlockObserver { private let filter: Filter private let workerQueue = OperationQueue() - private var observers = Array() + private var observers = Array() public init(mode: MOCObservationMode, filter: Filter, diff --git a/Sources/FFCoreData/NSFetchedResultsController/FetchedResulsControllerDelegate.swift b/Sources/FFCoreData/NSFetchedResultsController/FetchedResulsControllerDelegate.swift index 4f1a64f..aefe357 100644 --- a/Sources/FFCoreData/NSFetchedResultsController/FetchedResulsControllerDelegate.swift +++ b/Sources/FFCoreData/NSFetchedResultsController/FetchedResulsControllerDelegate.swift @@ -38,9 +38,9 @@ public class FetchedResultsControllerManager: NSOb public typealias Delegate = FetchedResultsControllerManagerDelegate public private(set) final weak var fetchedResultsController: Controller? - public final weak var delegate: Delegate? + public final weak var delegate: (any Delegate)? - internal init(fetchedResultsController: Controller, delegate: Delegate?) { + internal init(fetchedResultsController: Controller, delegate: (any Delegate)?) { self.fetchedResultsController = fetchedResultsController self.delegate = delegate super.init() @@ -63,13 +63,16 @@ public class FetchedResultsControllerManager: NSOb internal func endUpdates() {} // MARK: - NSFetchedResultsControllerDelegate - @objc public dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + @objc public dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController) { delegate?.controllerWillChangeContent?(controller) beginUpdates() } @objc(controller:didChangeSection:atIndex:forChangeType:) - public dynamic func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { + public dynamic func controller(_ controller: NSFetchedResultsController, + didChange sectionInfo: any NSFetchedResultsSectionInfo, + atSectionIndex sectionIndex: Int, + for type: NSFetchedResultsChangeType) { switch type { case .insert: insertSection(at: sectionIndex) case .update: updateSection(at: sectionIndex) @@ -80,7 +83,7 @@ public class FetchedResultsControllerManager: NSOb } @objc(controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:) - public dynamic func controller(_ controller: NSFetchedResultsController, + public dynamic func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, @@ -95,12 +98,12 @@ public class FetchedResultsControllerManager: NSOb delegate?.controller?(controller, didChange: anObject, at: indexPath, for: type, newIndexPath: newIndexPath) } - @objc public dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + @objc public dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { endUpdates() delegate?.controllerDidChangeContent?(controller) } - @objc public dynamic func controller(_ controller: NSFetchedResultsController, + @objc public dynamic func controller(_ controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? { delegate?.controller?(controller, sectionIndexTitleForSectionName: sectionName) ?? controller.sectionIndexTitle(forSectionName: sectionName) } diff --git a/Sources/FFCoreData/NSFetchedResultsController/NSFetchedResultsPublisher.swift b/Sources/FFCoreData/NSFetchedResultsController/NSFetchedResultsPublisher.swift index dc249fb..df8249b 100644 --- a/Sources/FFCoreData/NSFetchedResultsController/NSFetchedResultsPublisher.swift +++ b/Sources/FFCoreData/NSFetchedResultsController/NSFetchedResultsPublisher.swift @@ -27,7 +27,7 @@ import FFFoundation extension NSFetchedResultsController { public struct Publisher: Combine.Publisher { public typealias Output = Array - public typealias Failure = Error + public typealias Failure = any Error private let delegate: PublisherControllerDelegate @@ -56,7 +56,7 @@ extension NSFetchedResultsController { @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) fileprivate final class PublisherControllerDelegate: NSObject, NSFetchedResultsControllerDelegate { let controller: NSFetchedResultsController - let subject = PassthroughSubject, Error>() + let subject = PassthroughSubject, any Error>() @Synchronized private var didFetch = false @@ -87,11 +87,11 @@ fileprivate final class PublisherControllerDelegate) { + private func send(from objects: Array) { (objects as? Array).map(subject.send) } - @objc dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + @objc dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { controller.fetchedObjects.map(send) } } diff --git a/Sources/FFCoreData/NSManagedObject/CoreDataDecodable.swift b/Sources/FFCoreData/NSManagedObject/CoreDataDecodable.swift index cbe6f84..7d39299 100644 --- a/Sources/FFCoreData/NSManagedObject/CoreDataDecodable.swift +++ b/Sources/FFCoreData/NSManagedObject/CoreDataDecodable.swift @@ -20,7 +20,6 @@ import CoreData -#if compiler(>=5.7) public protocol CoreDataDecodable: Decodable { associatedtype DTO: Decodable @@ -31,21 +30,9 @@ public protocol CoreDataDecodable: Decodable { mutating func update(from dto: DTO) throws } -#else -public protocol CoreDataDecodable: Decodable { - associatedtype DTO: Decodable - - @discardableResult - static func findOrCreate(for dto: DTO, in context: NSManagedObjectContext) throws -> Self - - init(with dto: DTO, in context: NSManagedObjectContext) throws - - mutating func update(from dto: DTO) throws -} -#endif extension CoreDataDecodable { - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { try self.init(with: DTO(from: decoder), in: .decodingContext(at: decoder.codingPath)) } } @@ -78,7 +65,7 @@ extension CoreDataDecodable where Self: NSManagedObject { /// Errors thrown during the process of decoding CoreData entities. public enum CoreDataDecodingError: Error, CustomStringConvertible { /// Thrown if a managed object context was missing during the decoding. - case missingContext(codingPath: Array) + case missingContext(codingPath: Array) public var description: String { switch self { @@ -106,7 +93,7 @@ extension NSManagedObjectContext { /// - Parameter codingPath: The coding path for which to request the decoding context. Only used for debugging purposes. /// - Returns: The current decoding context. /// - Throws: ``CoreDataDecodingError/missingContext`` - public static func decodingContext(at codingPath: Array = []) throws -> NSManagedObjectContext { + public static func decodingContext(at codingPath: Array = []) throws -> NSManagedObjectContext { if let context = _decodingContext { return context } throw CoreDataDecodingError.missingContext(codingPath: codingPath) } diff --git a/Sources/FFCoreData/NSManagedObjectContext/NSManagedObjectContext+Helpers.swift b/Sources/FFCoreData/NSManagedObjectContext/NSManagedObjectContext+Helpers.swift index 1e433d5..be45ef1 100644 --- a/Sources/FFCoreData/NSManagedObjectContext/NSManagedObjectContext+Helpers.swift +++ b/Sources/FFCoreData/NSManagedObjectContext/NSManagedObjectContext+Helpers.swift @@ -23,7 +23,7 @@ import CoreData extension NSManagedObjectContext { public final func sync(do work: () throws -> T) rethrows -> T { try { - var result: Result! + var result: Result! performAndWait { result = Result(catching: work) } diff --git a/Sources/FFCoreData/UIKit/DataSources/CollectionViewDataSource.swift b/Sources/FFCoreData/UIKit/DataSources/CollectionViewDataSource.swift index 39e00bd..f02792e 100644 --- a/Sources/FFCoreData/UIKit/DataSources/CollectionViewDataSource.swift +++ b/Sources/FFCoreData/UIKit/DataSources/CollectionViewDataSource.swift @@ -32,7 +32,10 @@ import class CoreData.NSFetchedResultsController @objc public protocol CollectionViewDataSourceDelegate: NSObjectProtocol { func collectionView(_ collectionView: UICollectionView, cellIdentifierForItemAt: IndexPath) -> String - func collectionView(_ collectionView: UICollectionView, configure cell: UICollectionViewCell, forItemAt indexPath: IndexPath, with: NSFetchRequestResult?) + func collectionView(_ collectionView: UICollectionView, + configure cell: UICollectionViewCell, + forItemAt indexPath: IndexPath, + with: (any NSFetchRequestResult)?) // See UICollectionViewDataSource @objc(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) @@ -49,9 +52,11 @@ public final class CollectionViewDataSource: NSObj public private(set) weak var collectionView: UICollectionView? public private(set) weak var fetchedResultsController: NSFetchedResultsController? - public weak var delegate: CollectionViewDataSourceDelegate? + public weak var delegate: (any CollectionViewDataSourceDelegate)? - public required init(collectionView: UICollectionView, controller: NSFetchedResultsController, delegate: CollectionViewDataSourceDelegate? = nil) { + public required init(collectionView: UICollectionView, + controller: NSFetchedResultsController, + delegate: (any CollectionViewDataSourceDelegate)? = nil) { self.fetchedResultsController = controller self.collectionView = collectionView self.delegate = delegate @@ -60,7 +65,7 @@ public final class CollectionViewDataSource: NSObj } @objc public override func responds(to aSelector: Selector) -> Bool { - let selectorToCheck = #selector(CollectionViewDataSourceDelegate.collectionView(_:viewForSupplementaryElementOfKind:at:)) + let selectorToCheck = #selector((any CollectionViewDataSourceDelegate).collectionView(_:viewForSupplementaryElementOfKind:at:)) if selectorToCheck == aSelector { return delegate?.responds(to: aSelector) ?? false } @@ -94,7 +99,7 @@ public final class CollectionViewDataSource: NSObj @available(iOS 9.0, *) @objc(collectionView:canMoveItemAtIndexPath:) public dynamic func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool { - let delegateResponds = delegate?.responds(to: #selector(CollectionViewDataSourceDelegate.collectionView(_:moveItemAt:to:))) + let delegateResponds = delegate?.responds(to: #selector((any CollectionViewDataSourceDelegate).collectionView(_:moveItemAt:to:))) return delegate?.collectionView?(collectionView, canMoveItemAt: indexPath) ?? delegateResponds ?? false } diff --git a/Sources/FFCoreData/UIKit/DataSources/TableViewDataSource.swift b/Sources/FFCoreData/UIKit/DataSources/TableViewDataSource.swift index 301fa53..84438a8 100644 --- a/Sources/FFCoreData/UIKit/DataSources/TableViewDataSource.swift +++ b/Sources/FFCoreData/UIKit/DataSources/TableViewDataSource.swift @@ -35,7 +35,10 @@ import class CoreData.NSFetchedResultsController @objc public protocol TableViewDataSourceDelegate: NSObjectProtocol { func tableView(_ tableView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> String - func tableView(_ tableView: UITableView, configure cell: UITableViewCell, forRowAt indexPath: IndexPath, with object: NSFetchRequestResult?) + func tableView(_ tableView: UITableView, + configure cell: UITableViewCell, + forRowAt indexPath: IndexPath, + with object: (any NSFetchRequestResult)?) // See UITableViewDataSource @@ -62,9 +65,11 @@ public final class TableViewDataSource: NSObject, public private(set) weak var tableView: UITableView? public private(set) weak var fetchedResultsController: NSFetchedResultsController? - public weak var delegate: TableViewDataSourceDelegate? + public weak var delegate: (any TableViewDataSourceDelegate)? - public required init(tableView: UITableView, controller: NSFetchedResultsController, delegate: TableViewDataSourceDelegate) { + public required init(tableView: UITableView, + controller: NSFetchedResultsController, + delegate: any TableViewDataSourceDelegate) { self.fetchedResultsController = controller self.tableView = tableView self.delegate = delegate @@ -92,7 +97,7 @@ public final class TableViewDataSource: NSObject, } @objc public dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - let selectorToCheck = #selector(TableViewDataSourceDelegate.tableView(_:titleForHeaderInSection:)) + let selectorToCheck = #selector((any TableViewDataSourceDelegate).tableView(_:titleForHeaderInSection:)) if let delegate = delegate, delegate.responds(to: selectorToCheck) { return delegate.tableView?(tableView, titleForHeaderInSection: section) } @@ -125,7 +130,7 @@ public final class TableViewDataSource: NSObject, @objc(tableView:canMoveRowAtIndexPath:) public dynamic func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - let selectorToCheck = #selector(TableViewDataSourceDelegate.tableView(_:moveRowAt:to:)) + let selectorToCheck = #selector((any TableViewDataSourceDelegate).tableView(_:moveRowAt:to:)) return delegate?.tableView?(tableView, canMoveRowAt: indexPath) ?? delegate?.responds(to: selectorToCheck) ?? false } diff --git a/Sources/FFCoreData/UIKit/Delegates/CollectionViewFetchedResultsControllerDelegate.swift b/Sources/FFCoreData/UIKit/Delegates/CollectionViewFetchedResultsControllerDelegate.swift index 6ee61c7..cea7bb9 100644 --- a/Sources/FFCoreData/UIKit/Delegates/CollectionViewFetchedResultsControllerDelegate.swift +++ b/Sources/FFCoreData/UIKit/Delegates/CollectionViewFetchedResultsControllerDelegate.swift @@ -29,7 +29,7 @@ public final class CollectionViewFetchedResultsControllerManager>(wrappedValue: Array(repeating: nil, count: parallelRuns)) + let failures = Synchronized>(wrappedValue: Array(repeating: nil, count: parallelRuns)) DispatchQueue.concurrentPerform(iterations: parallelRuns) { iteration in do { try testObjects[iteration].ctx.asDecodingContext { [data = testObjects[iteration].data] in diff --git a/Tests/FFCoreDataTests/Models/TestConfiguration.swift b/Tests/FFCoreDataTests/Models/TestConfiguration.swift index e76ee9b..bb5fd4f 100644 --- a/Tests/FFCoreDataTests/Models/TestConfiguration.swift +++ b/Tests/FFCoreDataTests/Models/TestConfiguration.swift @@ -91,11 +91,7 @@ extension CoreDataStack.Configuration { try FileManager.default.createDirectoryIfNeeded(at: compilationFolder) defer { try? FileManager.default.removeItem(at: compilationFolder) } let deploymentTarget: String -#if compiler(>=5.7) deploymentTarget = "10.13" -#else - deploymentTarget = "10.12" -#endif _ = try Process.xcrun(arguments: [ "momc", "--sdkroot", sdkPath,