Monitoring for iOS with MetricKit: Getting Started
Learn how to use MetricKit to monitor power, performance, and diagnostics in your iOS apps. By Adam Rush.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Monitoring for iOS with MetricKit: Getting Started
15 mins
- Looking Inside MetricKit
- New APIs in iOS 14
- Getting Started
- Understanding MetricKit APIs
- Understanding MXMetricManager
- Implementing MXSignpostMetric
- Implementing MXMetricPayload
- Understanding MXDiagnosticPayload
- Understanding MXCallStackTree
- Understanding MXAppExitMetric
- Viewing in Organizer
- Generating Graphs & Reports
- Where to Go From Here?
For a long time, iOS apps were considered too small and lightweight to worry about monitoring their performance, unlike web apps. In modern times, however, iOS apps are getting bigger, more complex and doing more behind the scenes than ever before. Being able to remotely monitor how your app is running on many different devices is now vital.
Some iOS monitoring tools have been available for a long time. For example, you could query the device to see what OS is running or whether it’s using a Wi-Fi or cellular connection. Another common practice is using a third-party analytics tool, like Firebase or New Relic, to help capture what a user’s experience is really like.
However, there’s so much more diagnostic information that could help iOS developers! Apple listened and, in iOS 13, introduced MetricKit. This tool makes it much easier to retrieve diagnostic data from the device and make it available for remote monitoring for iOS. In this tutorial, you’ll explore MetricKit APIs by using Xcode to simulate receiving diagnostics.
You’ll do this by:
- Adding MetricKit to the starter app.
- Loading sample data into Xcode.
- Using MetricKit to view app use statistics.
Looking Inside MetricKit
With MetricKit, you can receive diagnostic data from the OS about the physical device. It sends a report containing data for the last 24 hours in JSON format. This is helpful, for example, if you want to relay this data to your own server. You can then visualize and analyze that data and use it to improve the performance of your app.
New APIs in iOS 14
MetricKit has been around since iOS 13, but it received some significant improvements in iOS 14.
First, it introduced a brand new payload called MXDiagnosticPayload
. Previously, you only received the MXMetricPayload
. The new MXDiagnosticPayload
gives much more information, such as crashes and exceptions. This is a great example of how Apple is continuously exploring ways of expanding frameworks.
Furthermore, Apple has introduced a brand-new performance metric called MXAppExitMetric
. This object represents the types of exits made by the app in the foreground and background. This information can help you discover why users are leaving your app and in what state.
Getting Started
Download the sample project using the Download Materials button at the top or bottom of this tutorial.
For this tutorial, you’ll work with Shopping Trolley, an app that displays a simple shopping list for different types of fruit — because the world clearly needs more fruit-listing apps. :]
Start by opening ShoppingListTableViewController.swift. Add this code to the top of the file.
import MetricKit
Now, you can access the MetricKit framework.
Next, add this code into viewDidLoad()
:
let metricManager = MXMetricManager.shared
metricManager.add(self)
This accesses MetricManager
provided by the framework. Then add self
, the ShoppingListTableViewController
, as a subscriber to metricManager
, which enables it to listen for a metrics payload from the OS.
Next, you just need to add one final piece of code to the bottom of ShoppingListTableViewController.swift.
extension ShoppingListTableViewController: MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXMetricPayload]) {
guard let firstPayload = payloads.first else { return }
print(firstPayload.dictionaryRepresentation())
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
guard let firstPayload = payloads.first else { return }
print(firstPayload.dictionaryRepresentation())
}
}
This is an extension on your view controller that conforms to MXMetricManagerSubscriber
, which contains two methods that can receive payloads from MetricKit. In this case, when receiving the payload, you simply print
it to the console. However, in your production version, this is a good place to record the metrics, such as making that API call to your server.
Congratulations! You’ve integrated MetricKit. :]
Understanding MetricKit APIs
The framework’s interface is straightforward, and you can see how much thought Apple put into it during development. The framework includes:
- A manager class with a subscriber protocol.
- Classes for each category of metrics and diagnostics.
- Payload classes for the reported data.
- Classes for measurement units, such as cellular bars. This is amazing!
- Classes for representing accumulated data such as histograms. They’ve done the hard work again!
Understanding MXMetricManager
MXMetricManager
is the beating heart of the MetricKit framework. This is the shared object that manages your subscription for receiving on-device daily metrics.
MetricKit starts accumulating reports for your app after calling shared
for the first time. To start receiving metrics reports, you first call add(_ :)
with a class that conforms to the MXMetricManagerSubscriber
protocol.
The system then delivers these reports once per day at most. Each report contains the metrics from the past 24 hours along with any previously undelivered reports.
The Manager also has remove(_ :)
, which allows you to remove a subscriber at any point.
Implementing MXSignpostMetric
A huge benefit of using the MetricKit framework is that you can now incorporate your very own metrics inside the ones Apple provides “out of the box”. This is incredibly powerful because it means you can add custom metrics to the reports you send to your server, allowing you to dig even further.
Start by adding a custom metric to log when a user loads ShoppingListTableViewController
. Add fruitsLogHandle
directly below the existing fruit
.
let fruitsLogHandle = MXMetricManager.makeLogHandle(category: "Fruits")
This creates a handle, which is a bit like a bucket, to hold your custom metrics.
Add this code to the end of viewDidLoad()
.
mxSignpost(
.event,
log: fruitsLogHandle,
name: "Loading Fruits TableViewController")
mxSignpost
logs a custom metric within the fruits bucket when the view controller has finished loading for the user. This is a simple example, and you could really expand on it further. For example, if your app has a video player, logging when the stream starts and finishes could be useful.
Implementing MXMetricPayload
You can receive two different payloads from MetricKit. Begin with MXMetricPayload
— this is an object that encapsulates the daily metrics report. Remember: To trigger the MetricKit daily report, you must have a real device plugged in! If you don’t have a device, you can still read through the tutorial, as a sample JSON payload is provided.
Build and run on the device.
Navigate back to Xcode and select Debug ▸ Simulate MetricKit Payloads.
This triggers a sample daily report with the same shape as the reports you’ll receive organically. You should see both of the payloads printed on the console.
AnyHashable("cellularConditionMetrics"): {
cellConditionTime = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 20;
bucketEnd = "1 bars";
bucketStart = "1 bars";
};
1 = {
bucketCount = 30;
bucketEnd = "2 bars";
bucketStart = "2 bars";
};
2 = {
bucketCount = 50;
bucketEnd = "3 bars";
bucketStart = "3 bars";
};
};
};
The code sample above reproduces a small snippet of the full payload showing cellularConditionMetrics
. This aspect of the payload provides rich data about the cellular conditions your user experienced during the last 24 hours of using your app. It also delves further by telling you how many times they were on one bar, two bars or three bars of service. You can use bucketCount
to create a histogram. Imagine how helpful it could be to know the average amount of time your users spend on certain bars of signal!
Understanding MXDiagnosticPayload
MXDiagnosticPayload
encapsulates diagnostics data given by the device, such as:
- Performance: crash reporting & exceptions
- Responsiveness: application hang rate
- Disk Access: disk read & writes
Inspect a sample of this payload.
[AnyHashable("crashDiagnostics"): <__NSArrayM 0x283764390>(
{
callStackTree = {
callStackPerThread = 1;
callStacks = (
{
callStackRootFrames = (
{
address = 74565;
binaryName = testBinaryName;
binaryUUID = "BE6FD323-B011-4E67-925B-A60362A1ADFA";
offsetIntoBinaryTextSegment = 123;
sampleCount = 20;
}
);
threadAttributed = 1;
}
);
};
diagnosticMetaData = {
appBuildVersion = 1;
appVersion = "1.0";
deviceType = "iPhone13,3";
exceptionCode = 0;
exceptionType = 1;
osVersion = "iPhone OS 14.4 (18D52)";
platformArchitecture = arm64e;
regionFormat = GB;
signal = 11;
terminationReason = "Namespace SIGNAL, Code 0xb";
virtualMemoryRegionInfo = "0 is not in any region. Bytes before following region: 4000000000 REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL UNUSED SPACE AT START ---> __TEXT 0000000000000000-0000000000000000 [ 32K] r-x/r-x SM=COW ...pp/Test";
};
version = "1.0.0";
}
The code sample above reproduces a small snippet of the full payload, showing crashDiagnostics
. This captures a crash the user experienced. It includes diagnosticMetaData
with helpful details like OS and app version.
Because this is a crash, the payload also has callStackTree
, which you’ll explore next.
Understanding MXCallStackTree
The more you explore MetricKit, the more you can see how it powers Apple’s own tools, such as Xcode Organizer. Once your app has gone live to users, you’re still able to see the reports generated by MetricKit in Organizer, even if you don’t import the framework.
As you can see, this app is live and has real data to explore right in Xcode. This is because Apple presents most metrics data for free. However, importing the MetricKit framework means you can leverage this data further and link it with your own metrics. It also means you have greater flexibility on how it’s displayed if your server powers custom visualizations.
MXCallStackTree
is a great example of data to leverage better via MetricKit. Not only do you get reports for things like crashes and exceptions, but you also get the StackTrace provided in JSON. This could be extremely useful in going further by linking crashes to actual stack traces, so you can fix those pesky bugs.
Understanding MXAppExitMetric
MXAppExitMetric
is a brand-new object provided in iOS 14 that represents how users are leaving your app. Exit types include:
- Foreground exit
- Background exit
Users could exit an app for many reasons. It could be an intentional decision to close the app, but sometimes the OS terminates the app — at worst, because of low memory or an exception.
In each category, you can get diagnostics on the number of:
- Normal App Exits: The app exited normally from the foreground or background.
- Abnormal App Exits: The app exited abnormally from the foreground or background.
- Memory Resource Limit: The system terminated the app from the foreground or background for using too much memory.
- Bad Access / Exception: The system terminated the app from the foreground or background for attempting an invalid memory access.
Remember: Each category is usually a property that then has relationships back to the histogram-structured data. Plus, it also links to other classes, such as MXStackTree
, which means you can link certain app exits back to actual crashes.
How powerful is that? Incredibly powerful!
Viewing in Organizer
Apple provides a certain level of data for free to visualize in Organizer. This is part of a project to incorporate data from App Store Connect right in Xcode.
You don’t get access to everything provided by MetricKit in Organizer, but you can expect to find:
- Crashes
- Disk Writes
- Energy Usage
- Battery Usage
- Hang Rate
- Launch Time
- Memory
- Scrolling
Apple has compiled the most commonly used pieces of data here, and hopefully, they’ll keep adding to this list.
Generating Graphs & Reports
Each section described above is already rendered into various charts. Usually, data is visualized as a histogram as well as a JSON payload. You can see how MetricKit data can be consumed and displayed to great effect.
A histogram for Scrolling metrics is shown above. It represents scrolling hitch time for your users, which is essentially how long it takes before they can scroll on your views. In this app, you can see a massive decrease in scroll hitching, but then a sudden increase. This is certainly something you’d want to explore and figure out what changed between those versions.
Here’s another example from a live app in the App Store. This particular graph displays Battery Usage, specifically battery usage for when users are active in the app.
The level of detail is quite amazing! You can see per section of your app — Networking, Display, etc. — what most affected battery usage. In this example, the app takes 8.29% of the user’s battery per day on average. It’s unclear what the benchmark would be here, but this gives you the power to explore and determine where to make improvements.
Where to Go From Here?
You can download the final project using the Download Materials button at the top or bottom of this tutorial.
MetricKit is a massive step forward in monitoring for iOS your live apps. To use effectively, it does require some thinking about what’s important for your users. For example, if your app has lots of content that performs scrolling — such as a news-reading app — then you might want to focus on scrolling metrics.
Once you’ve determined what’s important, you can decide how to consume that data and how to present it. For example, you could have a small Vapor Swift API that consumes the data and stores it in a database with a small front end dashboard that converts it into histograms. Integrating MetricKit itself is the easiest part! :]
For an in-depth video course on using Vapor with MetricKit, check out a fully working Swift Vapor API for monitoring for iOS.
Want to learn more? WWDC has a fantastic video on What’s New in MetricKit.
Now on to monitoring for iOS apps and improving your users’ experience! If you have questions or comments, please join the discussion below.