@@ -26,29 +26,101 @@ public extension Query {
2626 /// An asynchronous sequence of query snapshots.
2727 ///
2828 /// This stream emits a new `QuerySnapshot` every time the underlying data changes.
29- @available ( iOS 18 . 0 , * )
30- var snapshots : some AsyncSequence < QuerySnapshot , Error > {
29+ @available ( macOS 15 . 0 , iOS 18 . 0 , watchOS 11 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
30+ var snapshots : QuerySnapshotsSequence {
3131 return snapshots ( includeMetadataChanges: false )
3232 }
3333
3434 /// An asynchronous sequence of query snapshots.
3535 ///
3636 /// - Parameter includeMetadataChanges: Whether to receive events for metadata-only changes.
37- /// - Returns: An `AsyncThrowingStream` of `QuerySnapshot` events.
38- @available ( iOS 18 . 0 , * )
39- func snapshots( includeMetadataChanges: Bool ) -> some AsyncSequence < QuerySnapshot , Error > {
40- return AsyncThrowingStream { continuation in
41- let listener = self
42- . addSnapshotListener ( includeMetadataChanges: includeMetadataChanges) { snapshot, error in
43- if let error = error {
44- continuation. finish ( throwing: error)
45- } else if let snapshot = snapshot {
46- continuation. yield ( snapshot)
37+ /// - Returns: A `QuerySnapshotsSequence` of `QuerySnapshot` events.
38+ @available ( macOS 15 . 0 , iOS 18 . 0 , watchOS 11 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
39+ func snapshots( includeMetadataChanges: Bool ) -> QuerySnapshotsSequence {
40+ return QuerySnapshotsSequence ( self , includeMetadataChanges: includeMetadataChanges)
41+ }
42+
43+ /// An `AsyncSequence` that emits `QuerySnapshot` values whenever the query data changes.
44+ ///
45+ /// This struct is the concrete type returned by the `Query.snapshots` property.
46+ ///
47+ /// - Important: This type is marked `Sendable` because `Query` itself is `Sendable`.
48+ @available ( macOS 15 . 0 , iOS 18 . 0 , watchOS 11 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
49+ @frozen
50+ struct QuerySnapshotsSequence : AsyncSequence , Sendable {
51+ public typealias Element = QuerySnapshot
52+ public typealias Failure = Error
53+ public typealias AsyncIterator = Iterator
54+
55+ @usableFromInline
56+ internal let query : Query
57+ @usableFromInline
58+ internal let includeMetadataChanges : Bool
59+
60+ /// Creates a new sequence for monitoring query snapshots.
61+ /// - Parameters:
62+ /// - query: The `Query` instance to monitor.
63+ /// - includeMetadataChanges: Whether to receive events for metadata-only changes.
64+ @inlinable
65+ public init ( _ query: Query , includeMetadataChanges: Bool ) {
66+ self . query = query
67+ self . includeMetadataChanges = includeMetadataChanges
68+ }
69+
70+ /// Creates and returns an iterator for this asynchronous sequence.
71+ /// - Returns: An `Iterator` for `QuerySnapshotsSequence`.
72+ @inlinable
73+ public func makeAsyncIterator( ) -> Iterator {
74+ Iterator ( query: query, includeMetadataChanges: includeMetadataChanges)
75+ }
76+
77+ /// The asynchronous iterator for `QuerySnapshotsSequence`.
78+ @available ( macOS 15 . 0 , iOS 18 . 0 , watchOS 11 . 0 , tvOS 18 . 0 , visionOS 2 . 0 , * )
79+ @frozen
80+ public struct Iterator : AsyncIteratorProtocol {
81+ public typealias Element = QuerySnapshot
82+ @usableFromInline
83+ internal let stream : AsyncThrowingStream < QuerySnapshot , Error >
84+ @usableFromInline
85+ internal var streamIterator : AsyncThrowingStream < QuerySnapshot , Error > . Iterator
86+
87+ /// Initializes the iterator with the provided `Query` instance.
88+ /// This sets up the `AsyncThrowingStream` and registers the necessary listener.
89+ /// - Parameters:
90+ /// - query: The `Query` instance to monitor.
91+ /// - includeMetadataChanges: Whether to receive events for metadata-only changes.
92+ @inlinable
93+ init ( query: Query , includeMetadataChanges: Bool ) {
94+ stream = AsyncThrowingStream { continuation in
95+ let listener = query
96+ . addSnapshotListener ( includeMetadataChanges: includeMetadataChanges) { snapshot, error in
97+ if let error = error {
98+ continuation. finish ( throwing: error)
99+ } else if let snapshot = snapshot {
100+ continuation. yield ( snapshot)
101+ }
102+ }
103+
104+ continuation. onTermination = { @Sendable _ in
105+ listener. remove ( )
47106 }
48107 }
49- continuation. onTermination = { @Sendable _ in
50- listener. remove ( )
108+ streamIterator = stream. makeAsyncIterator ( )
109+ }
110+
111+ /// Produces the next element in the asynchronous sequence.
112+ ///
113+ /// Returns a `QuerySnapshot` value or `nil` if the sequence has terminated.
114+ /// Throws an error if the underlying listener encounters an issue.
115+ /// - Returns: An optional `QuerySnapshot` object.
116+ @inlinable
117+ public mutating func next( ) async throws -> Element ? {
118+ try await streamIterator. next ( )
51119 }
52120 }
53121 }
54122}
123+
124+ // Explicitly mark the Iterator as unavailable for Sendable conformance
125+ @available ( * , unavailable)
126+ extension Query . QuerySnapshotsSequence . Iterator : Sendable { }
0 commit comments