Reputation: 2134
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:
Is there a reason why one might prefer one over the other? Apple's own examples often use structs + @State
+ @Bindings
.
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?
When would one go for a reference type, when Bindings give pseudo-reference attributes to a value type?
Upvotes: 2
Views: 507
Reputation: 30401
struct
s are cheaper to create than class
es 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 struct
s.
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 struct
s because they are faster and less prone to bugs (see more detail above). In my apps I built / am building, I very rarely used ObservableObject
s. 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, struct
s 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 ObservableObject
s 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 ObservableObject
s 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 struct
s everywhere possible, and switch to class
if it solves a problem that is too complex to solve just with struct
s.
Upvotes: 3