iOS performance integration guide

Step-by-step instructions for adding performance monitoring to your iOS projects.

The BugSnag Performance iOS integration automatically instruments app starts, UIKit screen loads and network requests. SwiftUI view loads and arbitrary operations can also be manually instrumented in your code.

New to BugSnag? Create an account

Looking for error monitoring? See our integration guide

Installation

Using CocoaPods

Add the BugsnagPerformance pod to your Podfile:

pod 'BugsnagPerformance'

Run pod install after updating your Podfile.

Using Swift Package Manager

Open your Xcode project and select FileAdd Packages…

Search for https://github.com/bugsnag/bugsnag-cocoa-performance as the package URL

Set Dependency Rule exact version to v1.10.2, then click Add Package.

Manual Installation

Clone the BugSnag Performance GitHub repository into a local directory providing the version tag after --branch:

git clone --branch v1.10.2 https://github.com/bugsnag/bugsnag-cocoa-performance

Drag BugsnagPerformance.xcodeproj into your Xcode workspace.

Select your project in the Project Navigator and in the project editor that appears, select your app’s target.

Under the General tab, click the Frameworks, Libraries and Embedded Content section’s + button and select BugsnagPerformance.framework (from BugsnagPerformance).

The latest available version of bugsnag-cocoa-performance is v1.10.2.

Basic configuration

Configure your API key by adding a bugsnag Dictionary to your Info.plist file:

Set the apiKey in your Info.plist

Or in XML:

<key>bugsnag</key>
<dict>
    <key>apiKey</key>
    <string>YOUR-API-KEY</string>
</dict>

This is the same API key and the same plist key used by the BugSnag Error Monitoring library.

You can find your API key in Project Settings from your BugSnag dashboard.

App Delegate

If your app implements an app delegate, import the BugsnagPerformance module and initialize BugsnagPerformance in the application:didFinishLaunchingWithOptions: method:

#import <BugsnagPerformance/BugsnagPerformance.h>

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [BugsnagPerformance start];
    return YES;
}
import BugsnagPerformance

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions:
                     [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        BugsnagPerformance.start()
        return true
    }
}

Like us, some other monitoring vendor SDKs make use of the Activity Tracing API. This API does not play well with multiple consumers. Attempting to use BugSnag Performance alongside one of these other SDKs may have unwanted impact on the data collected.

Instrumenting app starts

BugSnag will automatically detect and report app starts, measuring them from the process start time for the app until the UIApplicationDidBecomeActiveNotification is received. These timings are shown under the “App starts” tab in the BugSnag Performance dashboard.

The instrumentation determines whether the app is being launched for the first time since installation or reboot and so is a full “cold” start or is otherwise a “warm” start. These two app start types are displayed separately on your BugSnag dashboard.

To disable automatic instrumentation, set the autoInstrumentAppStarts configuration option:

BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.autoInstrumentAppStarts = NO;
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.autoInstrumentAppStarts = false
BugsnagPerformance.start(configuration: config)

Instrumenting view loads

BugSnag will automatically detect and report UIViewController loads, measuring the time taken by any controllers in your app between loadView and viewDidAppear being called. These timings are shown under the “Screen loads” tab in the BugSnag Performance dashboard.

See our SwiftUI guide for instructions on setting up instrumentation for SwiftUI based apps.

Automatic capture of view loads is enabled by default and can be disabled using the autoInstrumentViewControllers configuration option:

BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.autoInstrumentViewControllers = NO;
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.autoInstrumentViewControllers = false
BugsnagPerformance.start(configuration: config)

This instrumentation relies on Objective-C method “swizzling” and is injected early in your app’s startup code. See our guide on Method swizzling usage for full details on which methods are affected and how it can be fully disabled if required.

If you want control over which views are automatically instrumented, you can implement a viewControllerInstrumentationCallback:

BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.viewControllerInstrumentationCallback = ^(UIViewController *viewController){
    return ![viewController isKindOfClass:[IgnoredViewController class]];
};
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.viewControllerInstrumentationCallback = { viewController in
    !(viewController is IgnoredViewController)
}
BugsnagPerformance.start(configuration: config)

When the callback returns false, the view will not be recorded. This also allows you to manually instrument the view in order to control the start and end of the measurement yourself.

Our automatic instrumentation does not include “container” view controllers (e.g. UITabViewController) as these don’t present any actual view themselves and so any performance impact of these views is out of a developer’s control. If you have custom container view controllers, these will be picked up in the automatic instrumentation and we recommend you exclude these using the above callback.

Pre-loaded views

In some cases, such as for screens in a UITabViewController, views will be “pre-loaded” by the OS before a user has navigated to them and made them visible. This results in a faster render if the view is later loaded, but presents some challenges for presenting and understanding measurements taken as they can occur a long time before the view load completes. Our instrumentation attempts to detect that a pre-load has occurred and adjusts the timings, marking the resulting span on your dashboard with “(pre-loaded)” to indicate that the span duration is quicker than it would otherwise have been.

Generic view controllers

If your app uses a UIViewController that is a Swift generic class, it is not possible to intercept the events necessary to measure the load period. Such view controllers can be instrumented manually using the bugsnagTraced API in the BugsnagPerformanceSwift package.

For example, with the following generic declaration:

class ViewControllerFromGeneric<T>: UIViewController {
    override func viewDidLoad() {
        // ...
    }
}

class SubclassedViewControllerFromGeneric: ViewControllerFromGeneric<AppConcreteClass> {
    // ...
}

The bugsnagTraced method should be used on each instance of the generic that you want to measure:

import BugsnagPerformanceSwift

@IBAction func showView(_ sender: Any) {

    let vc = ViewControllerFromGeneric<AppConcreteClass>().bugsnagTraced("an optional name")
    // or:
    // let vc = SubclassedViewControllerFromGeneric().bugsnagTraced("an optional name")

    show(vc, sender: sender)
}

SwiftUI

If your app uses SwiftUI, view load measurements can be easily captured on your BugSnag dashboard using our SwiftUI package. See our SwiftUI guide for installation and usage instructions.

Manual instrumentation

To instrument view loads manually, call startViewLoadSpan with a view name and type:

- (void)viewDidLoad {
    self.span = [BugsnagPerformance
                 startViewLoadSpanWithName:@"MyViewController"
                 viewType:BugsnagPerformanceViewTypeUIKit];
    [super viewDidLoad];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.span end];
}
override func viewDidLoad() {
    span = BugsnagPerformance.startViewLoadSpan(
        name: "MyViewController", viewType: .uiKit)
    super.viewDidLoad()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated);
    span?.end()
    span = nil
}

Instrumenting network requests

BugSnag will automatically detect and report network requests in your app made through URLSession. These timings are shown under the “Network requests” tab in the BugSnag Performance dashboard.

Automatic capture of network requests is enabled by default and can be disabled using the autoInstrumentNetworkRequests configuration option:

BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.autoInstrumentNetworkRequests = NO;
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.autoInstrumentNetworkRequests = false
BugsnagPerformance.start(configuration: config)

This instrumentation relies on Objective-C method “swizzling” and is injected early in your app’s startup code. See our guide on Method swizzling usage for full details on which methods are affected and how it can be fully disabled if required.

Manual instrumentation

To instrument network requests manually, call reportNetworkRequestSpan from your URLSessionTaskDelegate URLSession:task:didFinishCollectingMetrics method:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                     didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
    [BugsnagPerformance reportNetworkRequestSpanWithTask:task metrics:metrics];
}
func urlSession(_ session: URLSession, task: URLSessionTask,
                didFinishCollecting metrics: URLSessionTaskMetrics) {
    BugsnagPerformance.reportNetworkRequestSpan(task: task, metrics: metrics)
}

The networkRequestCallback configuration option allows you to control the data sent in these network request spans using a callback.

Sending custom spans

To send custom spans to BugSnag for any other operations you wish to measure, use BugsnagPerformance.startSpan to start a span, and call the span’s end method to end the measurement:

BugsnagPerformanceSpan *span = [BugsnagPerformance startSpanWithName:@"login"];
[api login:^(BOOL success){
    [span end];
}];
let span = BugsnagPerformance.startSpan(name: "login")
api.login { _ in
    span.end()
}

The options parameter allows you to customize some elements of the span’s behavior:

Reporting child-only spans

If a custom span is “first class”, its performance characteristics will be aggregated and shown in the “Custom” tab of the BugSnag dashboard. If the custom span is useful only for adding insight into the performance of its parent (through the waterfall diagram on the span instance page), you should set the isFirstClass span option to false:

BugsnagPerformanceSpanOptions *options = [BugsnagPerformanceSpanOptions new];
options.firstClass = BSGFirstClassNo;
[BugsnagPerformance startSpanWithName:@"always-nested" options:options];
BugsnagPerformance.startSpan(
    name: "always-nested",
    options: BugsnagPerformanceSpanOptions.setFirstClass(BSGFirstClass.no))

Controlling span hierarchies

When viewing a single instance of a span in the BugSnag dashboard, a waterfall diagram will show you that instance’s children and their children, and so on. This allows you to see in more detail where the time was spent, and aid diagnosing and fixing any problems. Usually this will be automatically managed by BugSnag, however when creating your own spans, you can use the options parameter to control their parent-child relationships to produce a meaningfully representative hierarchy in your dashboard. See Maintaining span context for more information.

Start and end time overrides

By default, a span will use the current timestamp as its start time. However, you can use the startTime span option to report spans that have already started by providing your own timestamp. You can also end a span with your own timestamp to provide a retrospective end time:

NSDate *taskStartTime = [NSDate date];

// When the task has completed:
NSDate *taskEndTime = [NSDate date];

BugsnagPerformanceSpanOptions *options = [BugsnagPerformanceSpanOptions new];
options.startTime = taskStartTime;
BugsnagPerformanceSpan *span1 = [BugsnagPerformance startSpanWithName:@"retrospective-span"
                                                              options:options];
[span1 endWithEndTime:taskEndTime];
let taskStartTime = Date()

// When the task has completed:
let taskEndTime = Date()

BugsnagPerformance.startSpan(
    name: "retrospective-span",
    options: BugsnagPerformanceSpanOptions.setStartTime(taskStartTime))
.end(endTime: taskEndTime)

Adding custom span attributes

Additional data can be added to spans to help diagnose performance issues. These are sent as span attributes and will be displayed on your Performance dashboard alongside the automatic span data captured by the SDK.

Screenshot of custom attributes panel

Attributes are added via a span object and are name value pairs where the type can be either a string, integer, double, boolean or an array of one of these types:

BugsnagPerformanceSpan *span = [BugsnagPerformance startSpanWithName:@"span-with-data"];
[span setAttribute:@"api.protocol" withValue:@"gql"];
[span setAttribute:@"api.version" withValue:@"v2"];
let span = BugsnagPerformance.startSpan(name: "span-with-data")
span.setAttribute("api.protocol", withValue: "gql")
span.setAttribute("api.version", withValue: "v2")

You can use a callback to set attributes when they are ending. This allows you to access all spans being generated, including those from automatic instrumentation:

BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
[config addOnSpanEndCallback:^BOOL(BugsnagPerformanceSpan * _Nonnull span) {
    [span setAttribute:@"device.locale" withValue: @"en-US"];
    return true;
}];
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.add { span in
    span.setAttribute("device.locale", withValue: "en-US");
    return true;
}
BugsnagPerformance.start(configuration: config)

The number and size of attributes added to spans are limited by the SDK to avoid oversized span payloads that would be rejected by the BugSnag API. See the Custom attribute limit configuration options to see how this can be customized if more data is required.

These callbacks can be executed with a high frequency, so care should be taken not to perform complex operations that will have a detrimental effect on performance. For more information, see the span callbacks configuration option.