Demystify SwiftUI Performance

Building a mental model for performance in SwiftUI

Performance feedback Loop

  • Symptoms – slowness, broken animation, or spinner
  • Measure – to get data
  • Identify cause
  • Optimize to fix the root cause – then re-measure to verify
  • Resolve the process

Pre-requisite  

  • Identify
  • View Lifetime and Value

Watch Demystify SwiftUI from WWDC. 2021

Dependencies

  • Understanding how a View is dependent on various views and their dependencies – all views ultimately resolve to a leaf view
  • The update process can be understand in the View Graph of dependencies, this includes Dynamic Properties (like @Environment property values), the process is recurves to go thru all the nodes which require updates.  This will replicate down thru to all the leafs.
  • To improve the process you can use _printChanges method in SwiftUI, you can set a break point and look at “expression Self._printChanges()” in lldb.  This is a debugging tool for best effort understanding of why a view changed.  You can also add a let _ = Self._printChanges() and it will write to the view console.  You MUST remove this code before submitting to the App Store
  • By extracting the views to only handle the specific item you will can get better updates
  • You can use @Observable which will also help reduce dependencies

Faster View updates

  • Analyze Hangs in Instruments from WWDC23 should be review ed
  • There is a tech talk on Hitches
  • They both originate from a slow update (like data filtering)
  • You shouldn’t do synchronous work in loading your data in a view (I should check this in my own apps)

Identify in List and Tables

  • These are complex advanced controls that can cause performance issues.  Understanding Identity is key to help improve this.
  • Identity helps swiftUI handle View timeline
    • Adding a ForEach in a list, as it will impact performance, it will need to know how many rows (views) to create, the Identity is used in creating this.
    • For Filters you should not do it inline as it will still need to create all of the rows.  You can move the filter into the ForEach but that is slow too.  So you should move the filter into the model.  That will be the fastest method.
  • Avoid using AnyView in ForEach
  • Flatten nested ForEach, except in Section Lists where SwiftUI will understand how to optimize this. ( can possibly use this for my category view in my own app)
  • These same rules work in Tables too
  • Number of Rows in Tables and Lists is Constant