How to improve the performance of your iOS app

Having a smooth and responsive application is one of those requirements that is not always easy to face. In this article, which is mostly based on my last experience at UIKonf18 in Berlin this May (I recommend to watch related talks like: “Gotta go fast”) and some personal experiences, we’ll walk-through the desirable points your UITableView/UIcollectionView (or app in general) needs to have.

The basics

Before starting with the recommendations, it is better to define and clarify the basic concept beyond this article, which is the main thread.

As you probably already know, the main thread shouldn’t be used for heavy operations, but mostly for:

  1. Accepting user input/interactions;
  2. Displaying results and updating the UI.

When the main thread has to deal with too many operations, the most common consequence is the pretty common phenomenon of dropped frames, which occurs when we cannot guarantee 60 fps (or in other words, a frame every ~16.67 ms).

How is it possible to debug and identify precisely dropped frames?

Sometimes it is very easy to spot them, since the most critical problem is unresponsiveness, but other times it isn’t and we need something more accurate to track them. For example using CADisplayLink (to track them directly with code in a very fast way) or in a more accurate way with the TimeProfiler.

To use CADisplayLink, you can simply use this class:

Then, access an instance of it inside the AppDelegate’s method: didFinishLaunchingWithOptions:

DroppingFramesHelper().activate()

Now, if you have any dropped frames, you can monitor them on the console:

Now you know that your frames drop, what can you do about it? There are a few measures you can take and in this article I suggest a few:

  1. Reduce the number of views and transparent views
  2. Minimise the load of work done in functions called continuously
  3. Decode JPEG images
  4. Off-screen rendering

Let’s discuss them one by one.

1. Reduce the number of views and transparent views

The first easy thing to do, in order to improve the performance in your application, is (whenever possible) to:

  • Reduce the number of views;
  • Avoid transparency.

An easy fix to the second point is simple:

label.layer.opacity = 1.0
label.backgroundColor = .white

To easily spot this overlapping of transparencies, we can rely on one very handy tool: Debug -> View Debugging -> Rendering -> Color Blended Layers.

This tool allows us to easily spot overlapping of views as in the following example:

With labels is very easy to forget to set the background colour to NOT clear when we don’t need it.

2. Minimise the load of work done in functions called continuously

Seems obvious, but functions like cellForItemAt indexPath or scrollViewDidScroll must be very fast since they are called continuously.

Be always sure to have the “dumbest” views/cells as possible, with configuration method(s) that are always very light. (e.g. no layout constraints involved, allocations of objects etc.)

3. Decode JPEG images

One of the “usual suspects” when we’re dealing with dropped frames issues, is the decoding of images. Usually, this operation is done on the main thread, under the hood, by imageViews.But sometimes this can lead to a consistent slowdown in our applications, especially when the images are pretty big.

To mitigate this problem, one solution is to move the decoding work to the background queue. In this way, the operations won’t be efficient as the normal decoding adopted by the UIImageView, but the mainThread will be free. Let’s see below some snippets taken from the project “Catstagram”, by Luke Parham:

Decode image in the background:

You can add some further cache control for more efficiency.

Which can be used as in the following class:

From there, you can use “AsyncImageView” instead of the classic “UIImageView” to have the decode of images in the background thread instead of the main one.

Why are we using the DispatchQueue.main.sync { }?

Since the memory warnings are handled on the main thread and we are working on a background one, if we don’t sync there is a huge risk of getting unexpected behaviour, such as crashes, if we’re using too much memory!

If you want to test and see the difference between the two approaches, use this playground.

Another solution (less didactic), is to simply use this library: https://github.com/rs/SDWebImage

4. Off-screen rendering

When we have to deal to specific properties of our UI Elements, we may have some off-screen rendering issues, due the fact that we need to prepare those elements before presenting them. Which means in other terms a heavy use of CPU and GPU.

How do we spot this problem?

As before, there’s another very handy tool: Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow.

With this instrument, we can immediately spot those elements, since they are highlighted with a yellow or red color, depending on how “heavy” they are.

A very typical example regards the use of the cornerRadius property. If you have something as follow in your app:

imageView.layer.cornerRadius = avatarImageHeight / 2.0

You may consider using UIBezierPath instead, which can simply resolve your off-screen rendering problems for that particular problem:

In short, try to:

  1. Avoid CornerRadius property;
  2. Avoid ShouldRasterize unless you really know it helps;
  3. Use .rounded() values whenever possible, because are more easy to compute.
  4. Keep in mind that also Shadows can cause off-screen rendering.

Other recommendations

Other general recommendation to keep in mind are:

  1. Text measurements (boudingRectWithSize) can be pretty heavy. Try to avoid them unless is very needed.
  2. Always check your hierarchy layout, especially if you’re using autolayout and you have to support older devices. (You may be interested in this UIKonf18 talk: “Improving Scrolling Performance through GPU Optimisation”)
  3. Whenever it is possible to put work in the background queue, do it! But keep an eye on memory warnings.

Conclusion

I hope that those tips are helpful for your projects and daily development life! Feel free to share your thoughts about it and to follow/ask me everything you need on Twitter!

Extra Resources