From 4429f7b1442cde402ac2a63fde3611599476ee0e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 29 Aug 2025 10:51:56 -0700 Subject: [PATCH 1/2] Perception checking: Fall back on current perception state (#3762) * Suppress more potential perception checks/warnings * wip --- Sources/ComposableArchitecture/Core.swift | 6 ++++-- .../Observation/IdentifiedArray+Observation.swift | 6 ++++-- Sources/ComposableArchitecture/Store.swift | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/ComposableArchitecture/Core.swift b/Sources/ComposableArchitecture/Core.swift index f853848df963..b50119322fb0 100644 --- a/Sources/ComposableArchitecture/Core.swift +++ b/Sources/ComposableArchitecture/Core.swift @@ -205,7 +205,7 @@ final class ScopedCore: Core { let stateKeyPath: KeyPath let actionKeyPath: CaseKeyPath #if DEBUG - let isInPerceptionTracking = _PerceptionLocals.isInPerceptionTracking + let initializedInPerceptionTracking = _isInPerceptionTracking #endif init( base: Base, @@ -220,7 +220,9 @@ final class ScopedCore: Core { @inline(__always) var state: State { #if DEBUG - return _PerceptionLocals.$skipPerceptionChecking.withValue(isInPerceptionTracking) { + return _PerceptionLocals.$skipPerceptionChecking.withValue( + initializedInPerceptionTracking || _isInPerceptionTracking + ) { base.state[keyPath: stateKeyPath] } #else diff --git a/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift b/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift index eafff521fe4c..70a39c3baa24 100644 --- a/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift +++ b/Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift @@ -92,7 +92,7 @@ extension Store where State: ObservableState { public struct _StoreCollection: RandomAccessCollection { private let store: Store, IdentifiedAction> private let data: IdentifiedArray - private let isInPerceptionTracking = _isInPerceptionTracking + private let initializedInPerceptionTracking = _isInPerceptionTracking #if swift(<5.10) @MainActor(unsafe) @@ -146,7 +146,9 @@ public struct _StoreCollection: RandomAc return child } #if DEBUG - return _PerceptionLocals.$isInPerceptionTracking.withValue(self.isInPerceptionTracking) { + return _PerceptionLocals.$isInPerceptionTracking.withValue( + self.initializedInPerceptionTracking || _isInPerceptionTracking + ) { child } #else diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index 9e15b0060418..5a9f9ed230a8 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -336,7 +336,7 @@ public final class Store: _Store { func subscribeToDidSet(_ type: T.Type) -> AnyCancellable { return core.didSet .prefix { [weak self] _ in self?.core.isInvalid == false } - .compactMap { [weak self] in (self?.currentState as? T)?._$id } + .compactMap { [weak self] in (self?.withState(\.self) as? T)?._$id } .removeDuplicates() .dropFirst() .sink { [weak self, weak parent] _ in From acd9bb8a7cf6e36a89d81a432c2e8eb3b1bb3771 Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:01:32 -0500 Subject: [PATCH 2/2] Allow store publisher to be used as async sequence. (#3763) * Allow store publisher to be used as async sequence. * Update StoreTests.swift * Update publisher to use withState for currentState --------- Co-authored-by: Stephen Celis --- Sources/ComposableArchitecture/Store.swift | 3 ++- Tests/ComposableArchitectureTests/StoreTests.swift | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index 5a9f9ed230a8..da3031689978 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -1,4 +1,5 @@ import Combine +import CombineSchedulers import Foundation import SwiftUI @@ -375,7 +376,7 @@ public final class Store: _Store { public var publisher: StorePublisher { StorePublisher( store: self, - upstream: self.core.didSet.map { self.currentState } + upstream: self.core.didSet.receive(on: UIScheduler.shared).map { self.withState(\.self) } ) } diff --git a/Tests/ComposableArchitectureTests/StoreTests.swift b/Tests/ComposableArchitectureTests/StoreTests.swift index 3df6642aed52..efd15ed960c4 100644 --- a/Tests/ComposableArchitectureTests/StoreTests.swift +++ b/Tests/ComposableArchitectureTests/StoreTests.swift @@ -1175,6 +1175,14 @@ final class StoreTests: BaseTCATestCase { XCTAssertNil(weakStore) } + @MainActor + func testPublisherAsyncSequence() async { + if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { + let store = Store(initialState: ()) {} + _ = await store.publisher.values.first { @Sendable _ in true } + } + } + @MainActor func testSharedMutation() async { XCTTODO(