@Published ReferenceType var

I noticed I can mark reference type variables @Published. But then nothing works: changing the contents of this variable doesn't update UI, view's body is not called, class Equatable method is not called.

  • is it supported to have reference type values @Published?

totally fine with me if it isn't, but then:

  • assuming the answer "no", is it a bug that I can mark reference type values @Published?
import SwiftUI

struct Some: Equatable {
    var x: Int = 0
    init(x: Int) {
        self.x = x
    }
    static func == (a: Self, b: Self) -> Bool {
        fatalError("to see if it's called")
    }
}

class MyClass: Equatable {
    var name: String
    var some = Some(x: 0)
    
    static func == (a: MyClass, b: MyClass) -> Bool {
        fatalError("to see if it's called")
    }
    
    init(name: String) {
        self.name = name
        
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] _ in
            some.x += 1 // neither this
            some = Some(x: some.x + 1) // nor that
            print("changing mine \(some.x)")
        }
    }
}

class Model: ObservableObject {
    static let singleton = Model()
    private init() {}
    @Published var item = MyClass(name: "Test")
}

struct ContentView: View {
    @ObservedObject var model = Model.singleton
    
    var body: some View {
        print("body")
        return Text(model.item.name + String(model.item.some.x)).padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This is expected and not a bug as, while observing the mutations of a references address isn't usually interesting, there may be a few cases where it is.

You mean "@Published" here is a no-op and is actually doing something useful? Can you provide an example?

No, I mean that, like all property wrappers, Published relies on mutation observation to trigger. Mutation the state of a reference type is not visible to this type of observation, so you see what you see. However, if you were to mutate the reference itself, perhaps by reassigning it, you should see a published value. Usually that's not useful at all but it could be sometimes, hence the lack of restriction on Published.Value.

1 Like

I see, indeed assigning the reference itself works.

changed version
import SwiftUI

struct Some: Equatable {
    var x: Int = 0
    init(x: Int) {
        self.x = x
    }
    static func == (a: Self, b: Self) -> Bool {
        fatalError("to see if it's called")
    }
}

class MyClass: Equatable {
    var name: String
    var some = Some(x: 0)
    
    static func == (a: MyClass, b: MyClass) -> Bool {
        fatalError("to see if it's called")
    }
    
    init(name: String, some: Some = Some(x: 0)) {
        self.name = name
        self.some = some
    }
}

class Model: ObservableObject {
    static let singleton = Model()
    private init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            let item = self.item
            item.some.x += 1
            self.item = item
            print("changing item \(item.some.x)")
        }
    }
    @Published var item = MyClass(name: "Test")
}

struct ContentView: View {
    @ObservedObject var model = Model.singleton
    
    var body: some View {
        print("body")
        return Text(model.item.name + String(model.item.some.x)).padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}