Reputation: 541
In SwiftUI, if I declare an array variable with the property wrapper @Published
, and then calculate each element of that array within a for()
loop, will the variable be published each time I compute a new element? If so, is there a way to tell the compiler to not publish the variable until every element is computed?
I have an app that computes a spectrum for each successive frame of audio data. The app then publishes that spectrum[]
to several Views that render fancy graphics. I want each View to re-draw only once for each successive frame of audio data - not once for each computed element of the spectrum[]
array.
Here's some simplified code to illustrate the problem:
class ArrayGenerator: ObservableObject {
@Published var spectrum = [Float](repeating: 0.0, count: 1000)
DispatchQueue.main.async { [self] in
for bin in 0 ..< 1000 {
spectrum[bin] = (userGain + userSlope * Float(bin)) * amplitudes[bin]
}
}
}
Since this code changes the variable 1,000 times, I believe it publishes it 1,000 times. But I want it to be published only once - when the for()
loop is completed. How can I accomplish this?
Upvotes: 0
Views: 321
Reputation: 30719
Your assumption that it will publish 1000 changes is correct. objectWillChange is sent in the willSet and although SwiftUI coalesces these notifications into one render it is still something you would want to avoid. The way to fix that is to simply do your work on a copy of the array and then set the published property with the new version of the array once.
Upvotes: 0
Reputation: 52555
Your assumption that it will publish 1000 changes/renders is incorrect. All of those iterations of the for
loop will be done in one iteration of the main run loop. Take this simple example:
class ArrayGenerator: ObservableObject {
@Published var spectrum = [Float](repeating: 0.0, count: 1000)
func run() {
DispatchQueue.main.async { [self] in
for bin in 0 ..< 1000 {
spectrum[bin] = Float(bin)
}
}
}
}
struct ContentView: View {
@StateObject private var generator = ArrayGenerator()
var body: some View {
let _ = print("Rendering...")
Text(generator.spectrum.map { String($0) }.joined())
.onAppear {
generator.run()
}
}
}
Which prints:
Rendering...
Rendering...
It renders once for the first appearance, then the onAppear
runs, and then an additional render after the loop is done.
So, your code already does what you expect.
Upvotes: 1