@Observable vs ObservableObject in SwiftUI

Published by malhal on

In SwiftIUI, when requiring the reference semantics of a class and wanting to upgrade from Combine’s ObservableObject and @StateObject to the new @Observable it requires a bit more work to get the life-cycle correct. The reason for this is we have to store the object in @State which is designed for structs and if we just init it inline like a struct as the @State‘s default value then we’ll have something similar to a memory leak because every time the View is init, another copy of the object will be init. And we know when working in Swift and SwiftUI, unnecessary object heap allocations should be avoided for performance reasons. Since we know that the old @StateObject is init in onAppear and deinit in onDisappear we can just implement that ourselves. See below for a code sample demonstrating the difference between using an ObservableObject class and an @Observable class in a SwiftUI view hierarchy. I have commented some points of interest.

import SwiftUI

@Observable class ObservableContent {
    var text1 = ""
    var text2 = ""
}

class ObservableObjectContent: ObservableObject {
    @Published var text1 = ""
    @Published var text2 = ""
}

struct ContentView: View {
    
    @State var observableContent: ObservableContent? // must be optional
    @StateObject var observableObjectContent = ObservableObjectContent() // this is init is actually delayed until the first appear, via @autoclosure. It is also automatically deinit in onDisappear.
    
    var body: some View {
        Group { // allows us to use modifiers on ifs (aka conditional views).
            if let observableContent { // required to deal with the optional state.
                ObservableContentView(content: observableContent)
            }
            ObservableObjectContentView(content: observableObjectContent)
        }
        .onAppear {
            if observableContent == nil { // Required because onAppear is also called on re-appear.
                observableContent = ObservableContent()
            }
        }
        .onDisappear {
            observableContent = nil // To be safe because when state is released is not documented.
        }
    }
}
struct ObservableContentView: View {
    @Bindable var content: ObservableContent
    
    var body: some View {
        Form {
            TextField("Text1", text: $content.text1)
            Text(content.text1)
        }
    }
    
}

struct ObservableObjectContentView: View {
    @ObservedObject var content: ObservableObjectContent
    
    var body: some View {
        Form {
            TextField("Text1", text: $content.text1)
            Text(content.text1)
        }
    }
}
Categories: SwiftUI