Observe values - withObservationTracking

Hi,

I have a class with a property. I would like to observe the value changes to this property.

The real purpose is to write test cases to check the value changes to this property when a function is executed.

Example:

@Observable
class A {
    var x = 100

    func f1() async {
        // changes the value of x multiple times
    }
}

Problem

  • Property observers such as willSet and didSet are great but I can't modify the class A.

Question

  • Is there a nice way to observe the value changes to x using Swift / Combine / Observation or any other way?
  • I prefer not to use NSObject if it can be avoided

Note: In the real world class A is annotated with the macro @Observable

Just to clarify your question.

This works:

class A {
    var x = 100 {
        didSet { print("x changed to \(x)") }
    }
}

and this also works:

class A: ObservableObject {
    @Published var x = 100 {
        didSet { print("x changed to \(x)") }
    }
}

but this doesn't work:

@Observable class A {
    var x = 100 {
        didSet { print("x changed to \(x)") }
    }
}

Did I understand you right?

Well I don't want to modify class A to add didSet

I would like to observe value changes without modifying class A

Thank you. Again, just to clarify, why don't you want modify class A? Is it not "yours" and it would be troublesome to ask the other team changing it or is there another reason?

Given the constraints (no NSObject, etc) polling seems to be the only available option (with all its drawbacks: CPU toll, possibility to miss quickly changing values, ...). (crossing that out as I forgot about Observable).

In real life classA is used as view model for SwiftUI view

And I would like to observe value of x in the test cases.

If possible I want to avoid adding code just to class A.

Certainly SwiftUI has a mechanism of observing changes and they have a function called withObservationTracking which looks promising but I can't get it to work.

Given below is my failed attempt:

import Foundation
import Observation

@Observable
class A {
    var x = 0
}

class Testing {
    let source = A()
    
    func observe() {
        print("observe started")
        withObservationTracking {
            _ = source.x
        } onChange: { [oldValue = source.x] in
            print("x oldValue: \(oldValue)")
        }
        print("observe ended")
    }
    
    func change() async {
        print("change started")
        try? await Task.sleep(for: .seconds(1))
        source.x = 20
        try? await Task.sleep(for: .seconds(1))
        source.x = 30
        try? await Task.sleep(for: .seconds(1))
        source.x = 40
        print("change ended")
    }
}

let testing = Testing()

Task {
    testing.observe()
}

await testing.change()

Output:

change started
observe started
observe ended
x oldValue: 0
change ended
Program ended with exit code: 0
1 Like

Here's a similar question.

1 Like