Optimize Your SwiftUI for Better Performance.

A view has a lifecycle. As the name suggests, it is a cycle 🔁. This cycle continues to happen while our app is running.

So,If there is expensive blocking in the cycle it will definitely reduce the performance of the app.

It is very important to have a good understanding of this cycle.Because then we can understand where to optimize.

So let’s first get an understanding of the life cycle of a view.😇

lifecycle of a View

View lifecycle and places to optimize

To begin with, let’s see what are views and their role in the SwiftUI system.

Everything we see on the screen is a view. SwiftUI manages the identity and the lifetime of your views. And because a view defines a piece of UI, it should be lightweight and inexpensive.

But,

“the lifetime of a view is separate from the lifetime of a struct that defines it. The struct you create that conforms to the View protocol actually has a very short lifetime. SwiftUI uses it to create a rendering, and then it’s gone.”

We next see how these changes that we see on view happen.

We get the new copy as the source of truth gets changed(mutated). (SwiftUI gets a new copy by calling the body of the view — not re-creating the view struct itself. So the subview structs will be recreated). As a result, we see view interact with us.

Summary — view get re-created as source of truth changed.

Now we have an idea about the lifecycle of a View. But, How to avoid slow updates⁉️

Considering two places at the lifecycle (check the above diagram). we can,

1️⃣ Avoid Slow Update

2️⃣ Pass expensive Work to Background Queue

1️⃣ Avoid Slow Update

Update behind the scene is creating a new copy of the view itself (calling the body of the view itself). So updates happen as the source of truth gets changed.

🔴 Since the SwiftUI calls the body of view (which we define or already built-in ), all the sub-view will be recreated.

When change happens to any view inside the body. the whole body (all the other subviews) will be re-generated.

We as developers should be responsible to let SwiftUI get a new copy of the view quickly.

In order to do that we can,

1. Make view initialization cheap

Since SwiftUI creates a new copy of the view every time when their source of truth is mutated.

2. Make the body a pure function (Do only creating view description)

This means that the body should be free of side effects ( we should not change any state inside from the body or do something else). That means you should simply create your view description and return it. No dispatching, no other work inside the body of the view struct. Just create some views and move on.

4. Avoid unnecessary heap allocation.

Allocating heap is expensive than stack allocation. If you are not familiar with what the heap and stack are, you can just think of it as a division of the memory (RAM).

This is not about reducing the space the app has to take in the memory. In here simply we need to avoid repeated heap allocation.

Please check my article about avoiding unnecessary heap allocation.

2️⃣ Pass expensive Work to Background Queue

There are multiple sources of the events (these cause event to happen)

  1. User interaction ( ex- tapping a button )
  2. Publisher firing( ex- Timer, Notification )
  3. onChange
  4. onOpenURL
  5. onContinueUserActivity

( 3,4,5 are SwiftUI 2.0 and above)

Each of these takes a closure as an argument and SwiftUI will run the closure at the right time.

🔴 Thing is that ,SwiftUI will run these closures on the main thread.

So if we are going to do expensive work. we should consider dispatching to a background queue.

🧑🏻‍🚀 One more thing — Avoid making assumptions about when and how often body is called. Remember, SwiftUI can be pretty smart sometimes, which means it might not call body in line with your assumptions.