Home > OS >  Local variables in view body
Local variables in view body

Time:12-03

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.

  • Related