diff --git a/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved b/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved index f5c649f2c58b..b93d9211ac23 100644 --- a/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/.github/package.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "92f2ded678a41ef5d8bc6b77a6f478ed09039d89ffc674e73012e9f30791ecb5", + "originHash" : "0e9a414ac23b15d7d00b87671275679eb6cce1d5dc101ec944f5e8aee4cb097b", "pins" : [ { "identity" : "combine-schedulers", @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { - "revision" : "ae208d1a5cf33aee1d43734ea780a09ada6e2a21", - "version" : "2.3.1" + "revision" : "4e89284c1966538109dc783497405bc680e9bc96", + "version" : "2.4.0" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "d924c62a70fca5f43872f286dbd7cef0957f1c01", - "version" : "1.6.0" + "revision" : "f4f57cac7d273cddf0161293d47adbb5a6ba3aed", + "version" : "2.0.0" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "75e846ee3159dc75b3a29bfc24b6ce5a557ddca9", - "version" : "2.5.2" + "revision" : "5d87dda90ed048f216826efbad404110141161bb", + "version" : "2.6.0" } }, { @@ -150,10 +150,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", - "version" : "1.5.2" + "revision" : "23e3442166b5122f73f9e3e622cd1e4bafeab3b7", + "version" : "1.6.0" } } ], - "version" : 2 + "version" : 3 } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecffae62a713..56fbf934ca50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,70 +46,6 @@ jobs: - name: Debug run: make XCODEBUILD_ARGUMENT="${{ matrix.command }}" CONFIG=Debug PLATFORM="${{ matrix.platform }}" WORKSPACE=.github/package.xcworkspace xcodebuild - xcodebuild: - name: xcodebuild (15) - runs-on: macos-14 - strategy: - matrix: - command: [test, ''] - platform: - - IOS - - MAC_CATALYST - - MACOS - - TVOS - # - VISIONOS # Unfortunately, visionOS on CI is too flakey - - WATCHOS - xcode: [15.2, 15.4] - exclude: - - {xcode: 15.2, command: test} - - {xcode: 15.4, command: ''} - - {xcode: 15.2, platform: MAC_CATALYST} - - {xcode: 15.2, platform: TVOS} - # - {xcode: 15.2, platform: VISIONOS} - - {xcode: 15.2, platform: WATCHOS} - steps: - - uses: actions/checkout@v4 - - name: Select Xcode ${{ matrix.xcode }} - run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - - name: Update xcbeautify - run: brew update && brew upgrade xcbeautify - - name: Install visionOS runtime - if: matrix.platform == 'visionOS' - run: | - sudo xcodebuild -runFirstLaunch - sudo xcrun simctl list - sudo xcodebuild -downloadPlatform visionOS - sudo xcodebuild -runFirstLaunch - - name: List available devices - run: xcrun simctl list devices available - - name: Cache derived data - uses: actions/cache@v3 - with: - path: | - ~/.derivedData - key: | - deriveddata-xcodebuild-${{ matrix.platform }}-${{ matrix.xcode }}-${{ matrix.command }}-${{ hashFiles('**/Sources/**/*.swift', '**/Tests/**/*.swift') }} - restore-keys: | - deriveddata-xcodebuild-${{ matrix.platform }}-${{ matrix.xcode }}-${{ matrix.command }}- - - name: Set IgnoreFileSystemDeviceInodeChanges flag - run: defaults write com.apple.dt.XCBuild IgnoreFileSystemDeviceInodeChanges -bool YES - - name: Update mtime for incremental builds - uses: chetan/git-restore-mtime-action@v2 - - name: Debug - run: make XCODEBUILD_ARGUMENT="${{ matrix.command }}" CONFIG=Debug PLATFORM="${{ matrix.platform }}" WORKSPACE=.github/package.xcworkspace xcodebuild - - library-evolution: - name: Library (evolution) - runs-on: macos-14 - steps: - - uses: actions/checkout@v4 - - name: Select Xcode 15.4 - run: sudo xcode-select -s /Applications/Xcode_15.4.app - - name: Update xcbeautify - run: brew update && brew upgrade xcbeautify - - name: Build for library evolution - run: make build-for-library-evolution - examples: name: Examples runs-on: macos-15 @@ -147,15 +83,3 @@ jobs: run: make DERIVED_DATA_PATH=~/.derivedData SCHEME="Todos" xcodebuild-raw - name: VoiceMemos run: make DERIVED_DATA_PATH=~/.derivedData SCHEME="VoiceMemos" xcodebuild-raw - - check-macro-compatibility: - name: Check Macro Compatibility - runs-on: macos-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Run Swift Macro Compatibility Check - uses: Matejkob/swift-macro-compatibility-check@v1 - with: - run-tests: false - major-versions-only: true diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved index 59644b749e41..0c42914f26d3 100644 --- a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "0479414dc97c4704849540dc64b58b99ce1f1c648e4cb9269d822b21cbc51b7f", + "originHash" : "658be5678358d678b69ea40e4be4814633be8197318d5ac54b97fb40cfb2152b", "pins" : [ { "identity" : "combine-schedulers", @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" + "revision" : "8c0c0a8b49e080e54e5e328cc552821ff07cd341", + "version" : "1.2.1" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-docc-plugin", "state" : { - "revision" : "d1691545d53581400b1de9b0472d45eb25c19fed", - "version" : "1.4.4" + "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", + "version" : "1.4.5" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { - "revision" : "ae208d1a5cf33aee1d43734ea780a09ada6e2a21", - "version" : "2.3.1" + "revision" : "4e89284c1966538109dc783497405bc680e9bc96", + "version" : "2.4.0" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "d924c62a70fca5f43872f286dbd7cef0957f1c01", - "version" : "1.6.0" + "revision" : "f4f57cac7d273cddf0161293d47adbb5a6ba3aed", + "version" : "2.0.0" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "75e846ee3159dc75b3a29bfc24b6ce5a557ddca9", - "version" : "2.5.2" + "revision" : "5d87dda90ed048f216826efbad404110141161bb", + "version" : "2.6.0" } }, { @@ -132,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", "state" : { - "revision" : "37230a37e83f1b7023be08e1b1a2603fcb1567fb", - "version" : "1.18.4" + "revision" : "d7e40607dcd6bc26543f5d9433103f06e0b28f8f", + "version" : "1.18.6" } }, { @@ -159,10 +159,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", - "version" : "1.5.2" + "revision" : "23e3442166b5122f73f9e3e622cd1e4bafeab3b7", + "version" : "1.6.0" } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.resolved b/Package.resolved index f5c649f2c58b..cebdfd5c9723 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "92f2ded678a41ef5d8bc6b77a6f478ed09039d89ffc674e73012e9f30791ecb5", + "originHash" : "0e9a414ac23b15d7d00b87671275679eb6cce1d5dc101ec944f5e8aee4cb097b", "pins" : [ { "identity" : "combine-schedulers", @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" + "revision" : "8c0c0a8b49e080e54e5e328cc552821ff07cd341", + "version" : "1.2.1" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-docc-plugin", "state" : { - "revision" : "d1691545d53581400b1de9b0472d45eb25c19fed", - "version" : "1.4.4" + "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", + "version" : "1.4.5" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { - "revision" : "ae208d1a5cf33aee1d43734ea780a09ada6e2a21", - "version" : "2.3.1" + "revision" : "4e89284c1966538109dc783497405bc680e9bc96", + "version" : "2.4.0" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "d924c62a70fca5f43872f286dbd7cef0957f1c01", - "version" : "1.6.0" + "revision" : "f4f57cac7d273cddf0161293d47adbb5a6ba3aed", + "version" : "2.0.0" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "75e846ee3159dc75b3a29bfc24b6ce5a557ddca9", - "version" : "2.5.2" + "revision" : "5d87dda90ed048f216826efbad404110141161bb", + "version" : "2.6.0" } }, { @@ -132,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "37230a37e83f1b7023be08e1b1a2603fcb1567fb", - "version" : "1.18.4" + "revision" : "d7e40607dcd6bc26543f5d9433103f06e0b28f8f", + "version" : "1.18.6" } }, { @@ -150,10 +150,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", - "version" : "1.5.2" + "revision" : "23e3442166b5122f73f9e3e622cd1e4bafeab3b7", + "version" : "1.6.0" } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.swift b/Package.swift index a9be7e1f27d7..815d8b2aef8a 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "1.1.0"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"), .package(url: "https://github.com/pointfreeco/swift-navigation", from: "2.3.0"), - .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.3.4"), + .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/swift-sharing", "0.1.2"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.3.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 4bb1790a82e4..06fff9013621 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "1.1.0"), .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"), .package(url: "https://github.com/pointfreeco/swift-navigation", from: "2.3.0"), - .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.3.4"), + .package(url: "https://github.com/pointfreeco/swift-perception", "1.3.4"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/swift-sharing", "0.1.2"..<"3.0.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.3.0"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), diff --git a/Sources/ComposableArchitecture/Core.swift b/Sources/ComposableArchitecture/Core.swift index 260649587cbb..0c1461c74193 100644 --- a/Sources/ComposableArchitecture/Core.swift +++ b/Sources/ComposableArchitecture/Core.swift @@ -61,9 +61,13 @@ final class RootCore: Core { self.reducer = reducer } func send(_ action: Root.Action) -> Task? { - _withoutPerceptionChecking { + #if DEBUG + _PerceptionLocals.$skipPerceptionChecking.withValue(true) { + _send(action) + } + #else _send(action) - } + #endif } private func _send(_ action: Root.Action) -> Task? { self.bufferedActions.append(action) diff --git a/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift b/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift index 7b7a9c0f725a..eafff521fe4c 100644 --- a/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift +++ b/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift @@ -92,6 +92,7 @@ extension Store where State: ObservableState { public struct _StoreCollection: RandomAccessCollection { private let store: Store, IdentifiedAction> private let data: IdentifiedArray + private let isInPerceptionTracking = _isInPerceptionTracking #if swift(<5.10) @MainActor(unsafe) @@ -120,28 +121,37 @@ public struct _StoreCollection: RandomAc ) return MainActor._assumeIsolated { [uncheckedSelf = UncheckedSendable(self)] in let `self` = uncheckedSelf.wrappedValue - guard self.data.indices.contains(position) - else { - return Store() - } - let elementID = self.data.ids[position] - let scopeID = self.store.id(state: \.[id: elementID], action: \.[id: elementID]) - guard let child = self.store.children[scopeID] as? Store - else { - @MainActor - func open( - _ core: some Core, IdentifiedAction> - ) -> any Core { - IfLetCore( - base: core, - cachedState: self.data[position], - stateKeyPath: \.[id: elementID], - actionKeyPath: \.[id: elementID] - ) + var child: Store { + guard self.data.indices.contains(position) + else { + return Store() + } + let elementID = self.data.ids[position] + let scopeID = self.store.id(state: \.[id: elementID], action: \.[id: elementID]) + guard let child = self.store.children[scopeID] as? Store + else { + @MainActor + func open( + _ core: some Core, IdentifiedAction> + ) -> any Core { + IfLetCore( + base: core, + cachedState: self.data[position], + stateKeyPath: \.[id: elementID], + actionKeyPath: \.[id: elementID] + ) + } + return self.store.scope(id: scopeID, childCore: open(self.store.core)) } - return self.store.scope(id: scopeID, childCore: open(self.store.core)) + return child } - return child + #if DEBUG + return _PerceptionLocals.$isInPerceptionTracking.withValue(self.isInPerceptionTracking) { + child + } + #else + return child + #endif } } } diff --git a/Sources/ComposableArchitecture/Observation/NavigationStack+Observation.swift b/Sources/ComposableArchitecture/Observation/NavigationStack+Observation.swift index 335d96218897..3d39c2db2aea 100644 --- a/Sources/ComposableArchitecture/Observation/NavigationStack+Observation.swift +++ b/Sources/ComposableArchitecture/Observation/NavigationStack+Observation.swift @@ -483,7 +483,7 @@ extension Store { @_spi(Internals) public var _isInPerceptionTracking: Bool { - #if !os(visionOS) + #if DEBUG && !os(visionOS) return _PerceptionLocals.isInPerceptionTracking #else return false diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index 4c14b3e862e0..6ca64ec00239 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -167,7 +167,13 @@ public final class Store: _Store { /// it conforms to ``ObservableState``. /// - Returns: The return value, if any, of the `body` closure. public func withState(_ body: (_ state: State) -> R) -> R { - _withoutPerceptionChecking { body(self.currentState) } + #if DEBUG + _PerceptionLocals.$skipPerceptionChecking.withValue(true) { + body(self.currentState) + } + #else + body(self.currentState) + #endif } /// Sends an action to the store. diff --git a/Tests/ComposableArchitectureTests/StorePerceptionTests.swift b/Tests/ComposableArchitectureTests/StorePerceptionTests.swift index bffa28d06251..b99bb3df0320 100644 --- a/Tests/ComposableArchitectureTests/StorePerceptionTests.swift +++ b/Tests/ComposableArchitectureTests/StorePerceptionTests.swift @@ -44,18 +44,10 @@ final class StorePerceptionTests: BaseTCATestCase { } } #if DEBUG && !os(visionOS) - let previous = Perception.isPerceptionCheckingEnabled - Perception.isPerceptionCheckingEnabled = true - defer { Perception.isPerceptionCheckingEnabled = previous } XCTExpectFailure { render(FeatureView()) } issueMatcher: { - $0.compactDescription == """ - failed - Perceptible state was accessed but is not being tracked. Track changes to state by \ - wrapping your view in a 'WithPerceptionTracking' view. This must also be done for any \ - escaping, trailing closures, such as 'GeometryReader', `LazyVStack` (and all lazy \ - views), navigation APIs ('sheet', 'popover', 'fullScreenCover', etc.), and others. - """ + $0.compactDescription.contains("Perceptible state was accessed") } #endif }