Translation of www.hackingwithswift.com/quick-start…

It is recommended to read the code landscape

Xcode’s Instruments tool comes with a nice set of SwiftUI analysis features that allow us to detect how often views are redrawn, which views have a slower body calculation, and how our state changes over time.

First we need something to help us look at the data in Instruments. The following code creates a timer that fires every 0.01 second and a button that displays a random UUID and a refresh count when clicked:

import Combine
import SwiftUI

class FrequentUpdater: ObservableObject {
    let objectWillChange = PassthroughSubject<Void.Never> ()var timer: Timer?

    init(a) {
        timer = Timer.scheduledTimer(
            withTimeInterval: 0.01,
            repeats: true
        ) { _ in
            self.objectWillChange.send()
        }
    }
}

struct ContentView: View {
    @ObservedObject var updater = FrequentUpdater(a)@State private var tapCount = 0

    var body: some View {
        VStack {
            Text("\(UUID().uuidString)")

            Button("Tap count: \(tapCount)") {
                tapCount + = 1}}}}Copy the code

Running the code in the emulator, you’ll notice that the interface is constantly being redrawn — as the observed values are constantly changing.

Note: This stress test is designed to allow SwiftUI to operate at high load to see the data we are interested in in Instruments — you should never do this in a real project.

Diagnostic code

Hit Cmd+I, run the code with Instruments, and select SwiftUI Instrument. When the Instrument window appears, click the Record button to launch the app. Watch for a while, click the button a few times, and then stop Instruments — we should have enough data here.

By default, SwiftUI Instrument provides the following information:

  1. How many views were created at run time and how much time it took to create them, i.e. (” View Body “)
  2. View Properties and how they change over time, i.e. (” View Properties “)
  3. How many Core Animation Commits have occurred, namely (” Core Animation Commits “)
  4. Time spent per function call (” Time Profiler “)

These instruments help you diagnose and resolve performance issues in your SwiftUI applications and are worth getting to know.

In our short stress test, we saw that View Body, View Properties, and Core Animation Commits were filled with brightly colored “walls.” Obviously, you need to be on your guard. It shows that not only is SwiftUI constantly rebuilding the view, but our properties are constantly changing so that Core Animation doesn’t have to work all the time.

Monitoring the body call

Select View Body Track — the first line in the Instruments list — and you should see that Instruments groups the monitoring results by SwiftUI and your project, the former being the original SwiftUI type, Like text views, buttons, and the latter contains your custom view types. In our example, a “ContentView” should appear in the custom view.

But you won’t see a perfect one-to-one mapping of your code to the SwiftUI view here, because SwiftUI uses a radical folding strategy for the view architecture to work as little as possible. So don’t expect to find the VStack creation code in there — it’s basically free in our app.

The important numbers on the screen are Count and Avg Duration — which indicate how many times each thing has been created and how long it took to create it, respectively. Since this is a stress test, you should expect to see a large Count, but our layout is very fragmented, so Avg Duration is probably only tens of microseconds.

Tracking state changes

Next, select View Properties Track, the second row in the Instruments list. It shows all attributes of all views, including their current values and all previous values.

Our example app contains a button that increases the count as it is clicked and displays it on the label. You can find the relevant Property in the instrument — look in the ContentView for the Property whose Property Type is State

.

Unfortunately, Instruments doesn’t seem to be able to display the exact property names for us, so if you have several states of the same type, such as integer states, it can be annoying. There is, however, a way to find the desired property: at the top of the recording window, there is an arrow marking the current view position. Try dragging the arrow, and you’ll see the state of your app change over time — every time you click the button, you’ll see an integer state increase, and you can fast-forward or rewind the process over and over again.

This feature is very powerful because it allows us to see state changes directly and how these processes can lead to slow redraws and other (performance issues). It’s almost like giving us a time machine — with which we can see the exact state of the app at every point in time.

Positioning is too slow to draw

Although SwiftUI can use Metal down the line to improve performance, in most cases it prefers Core Animation to render. This means that built-in Core Animation analysis tools, including detection of expensive commit overhead, are automatically available from Instruments.

Core Animation works best when multiple changes are placed into a single group, also known as “transactions.” Instead, we heap some work into a transaction and ask the CA to handle the rendering — a process known as a “commit” transaction.

So when Instruments shows us an expensive Core Animation commit, it’s really showing us the number of times SwiftUI has been forced to redraw on the screen due to updates. In theory, a redraw should only happen if some actual state of our application changes, resulting in a different view hierarchy, based on SwiftUI being able to compare the new body property output to the previous output.

Find function calls that are too slow

The last important track is the Time Profiler, which shows us the exact running times of various parts of our code. It works like a regular time analyzer in Instruments. In case you haven’t tried one, here are a few things to know:

  1. By default, the details window that expands on the right shows you the heaviest stack trace, the code that consumes the most runtime. The brightly colored code is the code you write, and the dull (gray) code is the system library code.
  2. On the left you can see all the threads that have been created, and they all have disclosure arrows that allow you to expand and see what functions they call, what functions they call, and so on. Usually, most work starts at “start”.
  3. To avoid displaying too many items, you may need to click the Call Tree button at the bottom and then select Hide System Libraries so that only the code you have written will be displayed. Unless your problem is caused by abuse or misuse of system libraries, this will help.
  4. To get to the details, you can also click on the Call Tree and select Invert Call Tree to Invert the leaf node’s functions – those at the end of the Call Tree – now appear at the top.

While time analysers are useful for locating performance problems, you generally only need to focus on the heaviest call stacks to find the biggest ones.

Some final tips

  1. When examining a part of your app, you should click and drag out of the scope to focus on the performance of a particular action, such as the response to a button click.
  2. What you see in Instruments are usually blocks of different colors, and this is just their perspective — you can see more details by holding down Cmd, clicking – and +.
  3. In order to obtain the most accurate image, samples are analyzed on a real machine.
  4. When changing code and resampling analysis, remember to make only one change at a time. If you make two changes, one will improve performance by 20% and the other will decrease performance by 10%, and you’ll end up thinking you’ve improved overall by 10%.
  5. Instruments runs your code in release mode, which will enable all Swift preferences and therefore affect debugging flags you add to your code, be aware of this.

For more content, please follow the public account “Swift Garden”.