EXPEDIA GROUP TECHNOLOGY — SOFTWARE

Monitoring App Performance on iOS

A guide to metrics and tools in the Apple app performance ecosystem

Corbin Montague
Expedia Group Technology
9 min readMar 23, 2021

--

the back of an iPhone
Photo by Aleksander Vlad on Unsplash

Why monitor app performance?

App performance has a significant impact on overall user experience. According to Apple®:

Customers expect apps to perform well. An app that takes a long time to launch, or responds slowly to input, may appear to the user as if it’s not working or is sluggish. An app that makes a lot of large network requests may increase the user’s data charges and drain the device battery. Any of these behaviors can frustrate users and lead them to uninstall the app.

decorative separator

What metrics should you monitor?

App Size

Although app size doesn’t have an in-app performance impact, it does have an impact on first time users looking to install your app, and is a constant strain on the limited mobile resources of your users’ disk space. More specifically, it can impact:

  1. The time it takes for users to download and install your app.
  2. The method in which users are able to download your app (Cellular or WiFi).
  3. The hard drive space consumed by keeping your app installed on a user’s device.
  4. The finances of your users depending on the size of your app, the method in which then downloaded it, and their cellular plan.

Apple documents how you can reduce your app’s size.

App Launch Time

There are three types of app launches on iOS:

  • Cold — Occurs after a device reboot or when the app is not loaded in memory.
  • Warm — Occurs when the app was recently terminated or is partially loaded in memory.
  • Resume — Occurs when the app was suspended and is still fully loaded in memory.

The operating system significantly optimizes warm launches and resumes, so our focus should be cold launches which are the longest of the three. A cold launch is also the experience first time users will have with your app and we want to make a great first impression! In a 2019 WWDC talk, Apple presses developers to strive for a 400ms cold app launch where the cold launch breaks down as follows:

  1. For the first 100ms, iOS will do necessary system-side work in order to initialize your app.
  2. Over the next 300ms, you should create your views, load your content, and generate your first frame (with placeholders if necessary).
  3. After the initial 400ms, your app should be launched and interactable. It’s ok to continue loading additional content afterwards to replace any placeholder views in your initial frame.

Apple also documents details on how you can reduce your app’s launch time.

App Responsiveness

It’s hard to state the importance of app responsiveness any better than Apple does:

An app that responds instantly to users’ interactions gives an impression of supporting their workflow. When the app responds to gestures and taps in real time, it creates an experience for users that they’re directly manipulating the objects on the screen. An app that doesn’t respond within a short time shatters this illusion, and leaves users wondering whether the app works correctly at all.

The human perception system is adept at identifying motion, and linking cause to effect through sequential actions. It doesn’t take long for a person to observe a gap between two events as a pause. Users can form the impression that an app is inert and unresponsive after a delay as short as a few tenths of a second. Apps therefore have to react to a user’s actions very quickly to maintain the user’s confidence in their behavior.

Monitoring app responsiveness can involve looking at a number of different metrics, most notably:

  • View load times
  • View update times
  • Scroll hitch rate
  • App hangs

Memory Usage

Memory is a limited resource, shared by apps, the operating system, processes, and the kernel. Because this limited resource is shared, one app using too much memory can compromise the user experience across the whole device. It’s for this very reason the OS sends a warning to apps using too much memory (which you can catch programmatically), and will even terminate them if necessary.

You can reduce your app’s memory use by following Apple’s guidance.

Battery Usage

Battery is another limited, shared resource on mobile devices. Unlike desktops which have a constant power supply, or laptops which can be charged while in use, mobile devices need to last for extended time periods without charging. To make matters worse, batteries lose capacity over time, further limiting this resource. When an app consumes too much power it can disrupt the user’s charging habits, forcing them to charge their phone at inopportune times. This negative experience can lead users to uninstall your app if they feel it does not provide enough value to offset its negative effect on their battery.

Apple has a collection of WWDC talks and articles on how to debug and write energy efficient apps.

Network Performance

Network performance can cover a wide variety of topics, but here are some key app performance metrics worth monitoring around networking:

  • The number of network requests sent by your app
  • The speed or round-trip time (RTT) of each network request
  • The amount of data requested by each network request

While the speed of network requests determines how quickly you can retrieve the data necessary to populate your view’s content during a load or update, the number of requests and amount of data requested can incur costs on your users who lack an unlimited data plan and likely affect your company’s finances as well.

There is a great 2020 WWDC talk on how you can boost both network performance and security in your app.

Disk I/O

Solid-State Drives (SSDs) are the permanent storage mechanism installed in every iOS device. Accessing data from SSD is always slower than accessing data stored in RAM and apps that make significant use of disk storage may find their app’s performance is negatively affected. SSD endurance also means that iOS devices can only write to the same region of the SSD so many times before that region is unusable, eventually wearing out the entire drive. In other words, apps that write to disk often not only risk slowing their own performance, but can also wear out the user’s SSD faster than expected, negatively affecting the user experience across the entire device.

Again, Apple documents strategies to reduce disk writes in your app.

decorative separator

What native iOS tools are available to automate monitoring these metrics?

XCTest Metrics

XCTest has a set of APIs for gathering performance metrics during your debug and testing phase. You can leverage them to automatically gather performance metrics within your unit and UI tests, set baseline and standard deviation values, and even fail tests if specific metrics start deviating too far from those baselines. This means if you have CI/CD workflows triggered by new PRs or commits, you can fail builds if they negatively affect your app’s performance! Apple has recommendations for schemes running these kinds of performance tests. Most notably, they should not attach the debugger or have diagnostic options turned on. Settings like these can add noise and skew your metrics.

screenshot of code measure{ XCUIApplication().launch() }

See Apple documentation here and this 2019 WWDC talk for details on using XCTest APIs for measuring app performance metrics within your unit and UI tests.

MetricKit

MetricKit is a native iOS framework for gathering app battery and performance metrics from your AppStore and TestFlight users. It takes very little code to start receiving daily reports on how your app is performing in the field for each user, while still giving you the flexibility to gather more custom metrics than what’s provided out of the box.

First you’ll need to declare an NSObject class that conforms to the MXMetricManagerSubscriber protocol:

import MetricKit
import os.log
/// A concrete implementation of `MXMetricManagerSubscriber` that listens for and processes daily app metrics.
class AppMetricManager: NSObject, MXMetricManagerSubscriber {

// MARK: - Dependencies

let metricManager: MXMetricManager

// MARK: - Init/Deinit

init(metricManager: MXMetricManager = MXMetricManager.shared) {
self.metricManager = metricManager

super.init()

// subscribe to receive metrics
metricManager.add(self)
}

deinit {
metricManager.remove(self)
}

// MARK: - MXMetricManagerSubscriber

// called at most once every 24hrs with daily app metrics
func didReceive(_ payloads: [MXMetricPayload]) {
os_log("Received Daily MXMetricPayload:", type: .debug)
for metricPayload in payloads {
if let metricPayloadJsonString = String(data: metricPayload.jsonRepresentation(), encoding: .utf8) {
os_log("%@", type: .debug, metricPayloadJsonString)

// Here you could upload these metrics (in JSON form) to your servers to aggregate app performance metrics
}
}
}

// called at most once every 24hrs with daily app diagnostics
@available(iOS 14.0, *)
func didReceive(_ payloads: [MXDiagnosticPayload]) {
os_log("Received Daily MXDiagnosticPayload:", type: .debug)
for diagnosticPayload in payloads {
if let diagnosticPayloadJsonString = String(data: diagnosticPayload.jsonRepresentation(), encoding: .utf8) {
os_log("%@", type: .debug, diagnosticPayloadJsonString)

// Here you could upload these metrics (in JSON form) to your servers to aggregate app performance metrics
}
}
}
}

Then, in your AppDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate, MXMetricManagerSubscriber {

private var appMetricManager: AppMetricManager?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
appMetricManager = AppMetricManager()

return true
}

}

And 💥… that’s it! Your AppMetricManager will get a callback about once every 24 hours with battery and performance metrics. Here you can transform the data and upload it to your own servers if you have internal dashboards for monitoring analytics around your app’s performance metrics.

See Apple documentation here and this 2020 WWDC talk for details on how you can use MetricKit to monitor app performance metrics for your TestFlight and AppStore users.

Xcode Metrics Organizer

Within Xcode’s Organizer window you’ll find aggregated battery and performance metrics that Apple automatically collects for your app. This data is provided to all iOS developers out of the box as of Xcode 11 and is collected for all of your users unless they have opted out of sharing data with developers (Settings → Privacy → Analytics & Improvements → Share With App Developers). While MetricKit allows you to programmatically track app performance metrics, not all developers need to track custom performance metrics or upload that data to their own servers. Xcode Metrics Organizer allows you to quickly and easily track some of the most critical app performance metrics and it doesn’t require you to do anything!

screenshot of battery usage histograms broken into categories audio, networking, processing, display, other

See Apple documentation here and this 2020 WWDC talk for details on analyzing the performance of your app within Xcode Organizer.

decorative separator

What third party tools are available to monitor these metrics?

Firebase Performance

Firebase Performance is a powerful app performance monitoring SDK from Google. With very little setup, you can monitor critical performance metrics that are gathered automatically around app launch speed, network requests, and screen rendering. It also gives developers the ability to monitor custom metrics similar to MetricKit (you can read more about that capability here). Although Apple has recently released some powerful native tools for monitoring app performance, Firebase Performance gives developers the ability to use the same tool in their iOS and Android apps while also automatically gathering metrics around network requests and screen rendering which Apple does not currently do.

See Google documentation here for details on how you can setup Firebase Performance in your iOS app.

SwiftInfo

SwiftInfo is a CLI tool that extracts, tracks, and analyzes useful metrics for Swift apps. It mostly helps gather codebase metrics (i.e. number of warnings, total lines of executable code, etc), but there are some app performance metrics you can gather as well like the size of your app catalogs, or overall app size. It plays nicely with fastlane which many iOS developers use to avoid interacting with Xcode CLTs directly, and can be integrated into your CI/CD workflows to automate the process of gathering these metrics.

You can find the Github repo for this library here.

Summary

Maintaining a great app is easier when you monitor key metrics and follow how they trend over time. It gives you the insight you need to determine where to best spend your time and resources reducing user frictions. There are many more metrics you can monitor and tools you can use than what I listed here, but hopefully this blog gets you on the right track to writing a wonderfully optimized iOS app!

--

--