RemoteConfig+Async.swift 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. // Copyright 2025 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. @available(iOS 13.0.0, macOS 10.15.0, macCatalyst 13.0.0, tvOS 13.0.0, watchOS 7.0.0, *)
  16. public extension RemoteConfig {
  17. /// Returns an `AsyncSequence` that provides real-time updates to the configuration.
  18. ///
  19. /// You can listen for updates by iterating over the stream using a `for try await` loop.
  20. /// The stream will yield a `RemoteConfigUpdate` whenever a change is pushed from the
  21. /// Remote Config backend. After receiving an update, you must call `activate()` to make the
  22. /// new configuration available to your app.
  23. ///
  24. /// The underlying listener is automatically added when you begin iterating and is removed when
  25. /// the iteration is cancelled or finishes.
  26. ///
  27. /// - Throws: An `Error` if the listener encounters a server-side error or another
  28. /// issue, causing the stream to terminate.
  29. ///
  30. /// ### Example Usage
  31. ///
  32. /// ```swift
  33. /// func listenForRealtimeUpdates() {
  34. /// Task {
  35. /// do {
  36. /// for try await configUpdate in remoteConfig.configUpdates {
  37. /// print("Updated keys: \(configUpdate.updatedKeys)")
  38. /// // Activate the new config to make it available
  39. /// let status = try await remoteConfig.activate()
  40. /// print("Config activated with status: \(status)")
  41. /// }
  42. /// } catch {
  43. /// print("Error listening for remote config updates: \(error)")
  44. /// }
  45. /// }
  46. /// }
  47. /// ```
  48. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  49. var configUpdates: some AsyncSequence<RemoteConfigUpdate, Error> {
  50. AsyncThrowingStream { continuation in
  51. let listener = addOnConfigUpdateListener { update, error in
  52. switch (update, error) {
  53. case let (update?, _):
  54. // If there's an update, yield it. We prioritize the update over a potential error.
  55. continuation.yield(update)
  56. case let (_, error?):
  57. // If there's no update but there is an error, terminate the stream with the error.
  58. continuation.finish(throwing: error)
  59. case (nil, nil):
  60. // If both are nil (the "should not happen" case), gracefully finish the stream.
  61. continuation.finish()
  62. }
  63. }
  64. continuation.onTermination = { @Sendable _ in
  65. listener.remove()
  66. }
  67. }
  68. }
  69. }