UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Why is a Class faster in SwiftUI than a Struct? (with example)

Forums > SwiftUI

Hi all,

can someone more experienced or smarter than me please take a look at this example code?

I was struggling with performance issues using SwiftUI with an MTKView and found out that the Struct I was using to keep the bindings was the guilty one. After hours of searching and trying stuff, I found out that using a Class solved the performance issues. It introduces other issues though, like the UI not always updating everywhere, and all my sliders returning to zero when my macOS app window is minimised or has no focus.

Even though I understand the difference between value and reference types, and that copying a Struct each time takes CPU cycles, I don't understand why my Class values seem to be unreachable at times by the UI...

Here's the macOS example code:

//
//  ContentView.swift
//  SlowStructFastClass
//
//  Created by Michel Storms on 12/01/2021.
//

import SwiftUI

class VC {
    var s1: Double = 0.5
}

class VD: ObservableObject {
    @Published var s1: Double = 0.5
}

struct ContentView: View {
    @State var valueStruct: Double = 0.5
    @State var valueClass = VC()
    @ObservedObject var valueClassObserved = VD()

    var body: some View {
        VStack {
            Text("STRUCT")
            Slider(value: $valueStruct)
            Text("\(self.valueStruct)")

            Text("CLASS")
            Slider(value: $valueClass.s1)
            Text("\(self.valueClass.s1)")

            Text("CLASS - OBSERVABLEOBJECT")
            Slider(value: $valueClassObserved.s1)
            Text("\(self.valueClassObserved.s1)")
        }
    }
}

I think this is the simplest I could make the example, yet there is still a big speed difference between struct and class binding-based sliders...

Any clues? What am I doing wrong?

Thanks for your time!

(tested on a 2020 i5 MBP running Big Sur)

edit: I added a case where I use ObservableObject / ObservedObject as suggested by Raja Kishan. This improves the responsiveness of the UI, but is still noticeable slower than the Class example.

2      

I see no difference whatsoever between the struct-based slider and the class-based.

What I do see is that you should not be using @State with a reference type, which you do here: @State var valueClass = VC(). @State is for value types.

I don't understand what you mean by this: "I don't understand why my Class values seem to be unreachable at times by the UI"

I also see that you are using an MTKView. Perhaps SwiftUI is interfering with the Metal rendering, as per this SO thread: SwiftUI updates reduce FPS of metal window?

2      

Hi,

there is a big difference though, but only on macOS... weirdly enough doing the same thing in an iOS emulator shows 3 sliders that behave the same.

The MTKView works perfectly, I get a solid 60fps when using the Class-based bindings, and about 6 when using the struct based ones. I understand that using a reference type as an @State is not ideal, but for now it is the only way I get decent sliders.

What I mean by the UI thing is a direct consequence of this, sadly. I mean that SwiftUI does not always update the view to whe binding. You can see this in my example, the Class based slider does not update its value text field...

I spent one of my 2 yearly code-level helps to get help from an Apple Engineer, something's definitely off or I am doing something very wrong here.

Will update here when I got news from them.

2      

I see no appreciable difference on macOS:

slow struct, fast class

What I mean by the UI thing is a direct consequence of this, sadly. I mean that SwiftUI does not always update the view to whe binding. You can see this in my example, the Class based slider does not update its value text field...

Using @State with a reference type is the cause of this. If you'll notice in my GIF, the text field connected to the middle slider doesn't update after sliding until you touch one of the other sliders. Because it uses @State with a reference type, which doesn't work to keep the values in synch; it will only update once either the @State used with a value type (i.e., the first slider) or the @ObservedObject used with a reference type (i.e., the third slider) changes.

2      

Yes I totally agree on the value / reference thing.

Thanks for taking the time for making that GIF. It does show the differemce though. The "class / middle" slider clearly sticks to the poinrer, where the others lag a bit behind. That lag gets way worse on an array of a few more sliders...

Its probably a result of the way SwiftUI works, Im just curious what Apple has to say about this and what they will offer as a workaround.

For those interested, here are 3 videos to compare the performance:

https://www.icloud.com/iclouddrive/0m2o4VTzRyD12IkKj0BJptIqg#Class_vs_Struct

This is literally on the same code, only the binding was chenged.

2      

Classes/ References instinctivly should always be faster for this kind of thing.

It's been a while since I have looked at SwiftUI but, sliders (pre-swiftui) used to either work on updating the value on the mouseup event or in "realtime" (excuse the incorrect specific terminology).

If it were doing the update in realtime wouldnt it necessitate creating multiple copies of the state across the entire MVVM data flow it's updating instead of just updating a single reference in place?

I'm pretty sure the memory overhead and performance overhead of that is incomprably slower but I am happy to be corrected.

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.