Philip Pegden
Philip Pegden

Reputation: 2134

SwiftUI architecture - value vs reference types

Putting aside the reasons why one might choose a value type over a reference type, does SwiftUI itself prefer use of @State vs @ObservedObject in terms of architecture.

Here for example are two implementations of a basic list app - cats implemented using a struct, dogs using a class:

import SwiftUI

struct Cat: Identifiable {
    let id = UUID()
    var name: String = "New cat"
}

final class Dog: Identifiable, ObservableObject {
    let id = UUID()
    @Published var name: String = "New dog"
}

final class Dogs: ObservableObject {
    @Published var dogs: [Dog] = []
}

struct ContentView: View {
    @State private var cats: [Cat] = []
    @ObservedObject private var dogs = Dogs()
    
    var body: some View {
        VStack {
            Text("Cats").bold()
            ForEach($cats, content: CatView.init)
            Button("New cat") {
                cats.append(Cat())
            }
            Divider().padding()
            Text("Dogs").bold()
            ForEach(dogs.dogs, content: DogView.init)
            Button("New dog") {
                dogs.dogs.append(Dog())
            }
        }
    }
}

struct CatView: View {
    @Binding var cat: Cat
    
    var body: some View {
        TextField("Name", text: $cat.name)
    }
}

struct DogView: View {
    @ObservedObject var dog: Dog
    
    var body: some View {
        TextField("Name", text: $dog.name)
    }
}

Very little difference in terms of coding...so:

Upvotes: 2

Views: 507

Answers (1)

George
George

Reputation: 30401

structs are cheaper to create than classes since there is no cost of references or ARC. Using structs also reduces the risk of shared-mutation bugs. See Choosing Between Structures and Classes (Apple developer article) on why to prefer structs.

Your code also has a bug in it which you may not have initially noticed. Since Dog is a class, and Dogs contains an array of Dog, the @Published here won't help in certain scenarios. This is because @Published detects a value type changing, e.g. the array having different elements. However, this doesn't account for mutating a Dog's name property. The Dog class reference in the array doesn't change so the @Published in Dogs doesn't emit an update. This is another bug caused by using a class instead of a struct. There is a way around this, but it introduces unnecessary complexity.


Is there a reason why one might prefer one over the other? Apple's own examples often use structs + @State + @Bindings.

Prefer structs because they are faster and less prone to bugs (see more detail above). In my apps I built / am building, I very rarely used ObservableObjects. They were the source of many bugs, unnecessary view updates, and complexity. The performance also greatly suffers when you have an ObservableObject with multiple @Published properties - changing one property updates every view which subscribes to the object updates.

Is there any performance implication for either choice, say when either Cat or Dog object has many fields (ObservedObjects let you precisely specify which attribute should generate an objectWillChange call), or if the arrays hold many items, i.e. 100k's?

The cost here is mutating the array of Cat or Dog, so I doubt there would be a performance difference to prefer class over struct. As mentioned earlier, structs are still more performant in general. Since in neither scenario the array is being copied (and if it was copied, it would again be the same) there is no difference.

When would one go for a reference type, when Bindings give pseudo-reference attributes to a value type?

Use ObservableObjects when you rely on sharing data across a section of your app and potentially need some logic to get/set the @Published properties within. I (personally) only ever use ObservableObjects when I need to pass the object through the environment at some point with @EnvironmentObject.


To finish off, choose what you think is best for your architecture. Prefer structs everywhere possible, and switch to class if it solves a problem that is too complex to solve just with structs.

Upvotes: 3

Related Questions