@Observable observation outside of SwiftUI

I've just started tinkering with @Observable and have run into a question... What is the best practice for observing an Observable object outside of SwiftUI? For example:

@Observable
class CountingService {
    public var count: Int = 0
}

@Observable
class ObservableViewModel {
    public var service: CountingService

    init(service: CountingService) {
        self.service = service
        
        // how to bind to value changes on service? 
    }

    // suggestion I've seen that doesn't smell right
    func checkCount() {
        _ = withObservationTracking {
            service.count
        } onChange: {
            DispatchQueue.main.async {
                print("count: \(service.count)")
                checkCount()
            }
        }
    }
}

Historically using ObservableObject I'd have used Combine to monitor changes to service. That doesn't seem possible with @Observable and I don't know that I've come across an accepted / elegant solution? Perhaps there isn't one? There's no particular reason that CountingService has to be @Observable -- it's just nice and clean.

Any suggestions would be appreciated!

Accepted Reply

Sadly, there's no way to do this at the moment other than the approach you've taken which I agree doesn't smell right.

As you've found, the only approach that works right now is as using withobservationtracking(_:onchange:). Any Observable property that you access in the apply method will trigger onChange when it changes. But it will only trigger it once, so you must call withobservationtracking(_:onchange:) again to re-register the observation.

Replies

Sadly, there's no way to do this at the moment other than the approach you've taken which I agree doesn't smell right.

As you've found, the only approach that works right now is as using withobservationtracking(_:onchange:). Any Observable property that you access in the apply method will trigger onChange when it changes. But it will only trigger it once, so you must call withobservationtracking(_:onchange:) again to re-register the observation.

Thanks for the confirmation Matt! It's a bummer that this is the current solution for the scenario above given the elegance of Observable.

I don't believe that it's intentional, but a number of the recent (and big) API additions by Apple (Observable, SwiftData, et al.) don't seem especially compatible with "traditional" patterns like MVVM, MVP and the like.

I don't believe that it's intentional

Indeed. This feature was designed (somewhat) in the open via SE-0395 Observation, and you can read the related review threads to understand how we ended up where we did.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"