In a SwiftUI View
's body
, I store intensive work in a local variable, which I use in its subviews (Like through a context menu).
I do this to avoid doing the expensive work multiple times per view refresh, if I use the variable multiple times in the view body.
Here is an example of what I'm doing:
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
let num = viewModel.expensiveWork
Text("Number: \(num)")
.contextMenu {
Button("Press me \(num)") {
viewModel.doWork()
}
}
.frame(width: 150, height: 100)
}
}
class ViewModel: ObservableObject {
@Published var num = 0
var expensiveWork: Int { num * 20 }
func doWork() {
num = Int.random(in: 0..<100)
}
}
It works fine right now, but I was just wondering if this is good practice, and if it could cause any desynchronization issues.
I apologize if this is a stupid question.
CodePudding user response:
Do the expensive work in the model and reference it. Structs are disposable; everything gets reinitialized all of the time and you can't control that.
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text("Number: \(viewModel.expensiveWork)")
.contextMenu {
Button("Press me \(viewModel.expensiveWork)") {
viewModel.doWork()
}
}
.frame(width: 150, height: 100)
}
}
class ViewModel: ObservableObject {
@Published var num = 0
var expensiveWork: Int = 0
func doWork() {
num = Int.random(in: 0..<100)
expensiveWork = num * 20
}
}
CodePudding user response:
Because expensiveWork
is a computed property and you are calling it in the view body
, you are still doing the work every time the view body
gets recomputed.
Code:
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text("Number: \(viewModel.expensiveWork)")
.contextMenu {
Button("Press me \(viewModel.expensiveWork)") {
viewModel.doWork()
}
}
.frame(width: 150, height: 100)
}
}
class ViewModel: ObservableObject {
@Published var num = 0
@Published var expensiveWork = 0
func doWork() {
num = Int.random(in: 0..<100)
Task {
// Intensive work here while it is asynchronous
let result = num * 20
await MainActor.run {
expensiveWork = result
}
}
}
}
Can it cause desynchronisation issues?
Yes - num
and expensiveWork
may be set at different times. To avoid this, change doWork()
to the below code:
func doWork() {
let temp = Int.random(in: 0..<100)
Task {
// Intensive work here while it is asynchronous
let result = temp * 20
await MainActor.run {
num = temp
expensiveWork = result
}
}
}
This comes at the trade-off that now num
won't update until the expensive work is done.