ObservableObject.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /// An object that can be observed for changes.
  2. ///
  3. /// The default implementation only publishes changes made to properties that
  4. /// have been wrapped with the ``Published`` property wrapper. Even properties
  5. /// that themselves conform to ``ObservableObject`` must be wrapped with the
  6. /// ``Published`` property wrapper for clarity.
  7. ///
  8. /// ```swift
  9. /// class NestedState: ObservableObject {
  10. /// // Both `startIndex` and `endIndex` will have their changes published to `NestedState`'s
  11. /// // `didChange` publisher.
  12. /// @Published
  13. /// var startIndex = 0
  14. ///
  15. /// @Published
  16. /// var endIndex = 0
  17. /// }
  18. ///
  19. /// class CounterState: ObservableObject {
  20. /// // Only changes to `count` will be published (it is the only property with `@Published`)
  21. /// @Published
  22. /// var count = 0
  23. ///
  24. /// var otherCount = 0
  25. ///
  26. /// // Even though `nested` is `ObservableObject`, its changes won't be
  27. /// // published because if you could have observed properties without
  28. /// // `@Published` things would get pretty messy and you'd always have to
  29. /// // check the definition of the type of each property to know exactly
  30. /// // what would and wouldn't cause updates.
  31. /// var nested = NestedState()
  32. /// }
  33. /// ```
  34. ///
  35. /// To use an observable object as part of a view's state, use the ``State`` property
  36. /// wrapper. It'll detect that it's been given an observable and will forward any
  37. /// observations published by the object's ``ObservableObject/didChange`` publisher.
  38. ///
  39. /// ```swift
  40. /// class CounterState: ObservableObject {
  41. /// @Published var count = 0
  42. /// }
  43. ///
  44. /// struct CounterView: View {
  45. /// @State var state = CounterState()
  46. ///
  47. /// var body: some View {
  48. /// HStack {
  49. /// Button("-") {
  50. /// state.count -= 1
  51. /// }
  52. /// Text("Count: \(state.count)")
  53. /// Button("+") {
  54. /// state.count += 1
  55. /// }
  56. /// }
  57. /// }
  58. /// }
  59. /// ```
  60. public protocol ObservableObject: AnyObject {
  61. /// A publisher which publishes changes made to the object. Only publishes changes made to
  62. /// ``Published`` properties by default.
  63. var didChange: Publisher { get }
  64. }
  65. extension ObservableObject {
  66. public var didChange: Publisher {
  67. let publisher = Publisher()
  68. .tag(with: String(describing: type(of: self)))
  69. var mirror: Mirror? = Mirror(reflecting: self)
  70. while let aClass = mirror {
  71. for (_, property) in aClass.children {
  72. guard
  73. property is PublishedMarkerProtocol,
  74. let property = property as? ObservableObject
  75. else {
  76. continue
  77. }
  78. let cancellable = publisher.link(toUpstream: property.didChange)
  79. cancellable.defuse()
  80. }
  81. mirror = aClass.superclassMirror
  82. }
  83. return publisher
  84. }
  85. }
  86. protocol OptionalObservableObject {
  87. var didChange: Publisher? { get }
  88. }
  89. extension Optional: OptionalObservableObject where Wrapped: ObservableObject {
  90. var didChange: Publisher? {
  91. switch self {
  92. case .some(let object):
  93. object.didChange
  94. case .none:
  95. nil
  96. }
  97. }
  98. }
  99. @available(*, deprecated, message: "Replace Observable with ObservableObject")
  100. public typealias Observable = ObservableObject
  101. /// Automatically observes all public noncomputed variables with public getter and setter
  102. //@attached(memberAttribute)
  103. // @attached(extension, conformances: ObservableObject)
  104. // public macro ObservableObject() =
  105. // #externalMacro(
  106. // module: "SwiftCrossUIMacrosPlugin",
  107. // type: "ObservableObjectMacro"
  108. // )
  109. /// Apply to a member inside your `@ObservableObject` class to opt out of observation
  110. // This macro is just used as a flag for `@ObservableObject` to ignore a specific property
  111. // @attached(accessor)
  112. // public macro ObservationIgnored() =
  113. // #externalMacro(
  114. // module: "SwiftCrossUIMacrosPlugin",
  115. // type: "ObservationIgnoredMacro"
  116. // )