Last active
July 26, 2025 08:48
-
-
Save JuniperPhoton/e68ffb1ec26042ca21e87dac3c389031 to your computer and use it in GitHub Desktop.
Observations for Pre-iOS 26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // NOTE: Though this works on my demo, be cautious when using this in production. | |
| /// Provides a way to observe value changes in a closure using ``AsyncSequence``, | |
| /// just like the [Observations](https://developer.apple.com/documentation/observation/observations) introduced in iOS 26 lineup. | |
| /// | |
| /// - Parameters: | |
| /// - action: A closure that returns a value to observe. It must read one or more values in an object with `@Observable` macro. | |
| /// - Returns: An `AsyncSequence` that yields the value returned by the action closure when it changes. | |
| @available(iOS 17.0, macOS 14.0, *) | |
| func observations<V>( | |
| action: @escaping () -> (V) | |
| ) -> any AsyncSequence<V, Never> { | |
| AsyncStream { continuation in | |
| var yieldedFirst = false | |
| withObservationTrackingInLoop(action: action) { value in | |
| // The first emitted value will be yielded immediately, | |
| // subsequent values will be yielded on change. | |
| if !yieldedFirst { | |
| yieldedFirst = true | |
| } else { | |
| continuation.yield(value) | |
| } | |
| } | |
| } | |
| } | |
| /// A withObservationTracking wrapper that will execute the same method in the current loop when the value changes. | |
| @available(iOS 17.0, macOS 14.0, *) | |
| func withObservationTrackingInLoop<V>( | |
| action: @escaping () -> (V), | |
| _ onChanged: @escaping (V) -> Void | |
| ) { | |
| // The action will be executed right away and return a value. | |
| let value = withObservationTracking { | |
| action() | |
| } onChange: { | |
| // This closure will be called whenever the value changes. | |
| // Then we schedule the onChanged closure to run on the same loop, | |
| // which will trigger the onChanged closure again. | |
| RunLoop.current.perform(inModes: [.common]) { | |
| withObservationTrackingInLoop(action: action, onChanged) | |
| } | |
| } | |
| // Call onChanged with the initial value. | |
| onChanged(value) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment