[Pitch] Observation (Revised)

I think Rx inspired libraries such as Combine handled this problem particularly elegantly:

// Combine attempt at implementing the observer
func startObserver() { // sync
    // 1. No need to display the current value. It's synchronous. Just as long
    //     as the publisher _immediately_ emits the current value. Your 2nd
    //     constraint is met
    // 2. Start observing for changes
    observablePublisher
      .sink { value in display(value) } // this is still synchronous on initial connect!
      .store(in: &cancellables)
}

I'm not saying we should go back to Combine necessarily. What I am saying is let's not 'throw the baby out with the bath water' and forget why the Rx/KVO libraries handled it this way initially.

Personally, the loss of intermediate values is not what concerns me – latest is fine. It's the loss of synchronisation between an object graph of Observables that's the bigger issue in my mind. We already know people use ObservableObject in this way, so there's good reason they will use Observables in the same way.

I've included a contrived example below. In this example when an Even number is displayed, the background of the view should always be red. When an Odd number is displayed it should always be green.

However, as the parent's observation of the ChildObservable's value property lags the View's observation of the ChildObservable's value property, the number and color will often fall out of sync and display an inappropriate color.

// OBSERVABLES 

@MainActor @Observable final class ChildObservable {
  var value = 0
  // synchronously fires the update to the view
  func plusOne() { value += 1 }
}

@MainActor @Observable final class ParentObservable {
  
  let subobservable = ChildObservable()
  var color = Color.red
  
  func startObserver() {
    Task {
      // asynchronously fires
      for await value in subobservable.changes(for: \.value) {
        // uh oh. value might be stale by now...
        self.color = value % 2 == 0 ? .red : .green
      }
    }
  }
}

// VIEW

@MainActor struct NumberView: View {
  
  @State private var model = ParentObservable()
  
  var body: some View {
    Text(verbatim: "\(model.subobservable.value)")
      .background(model.color)
  }
}