# Mobile App Monitoring for iOS Applications Manager Mobile APM for iOS lets you track the performance of your native mobile applications on actual end-user devices. For example, a news reader application may perform the following operations internally: - Navigate to a table view to show a list of articles - Load the list of articles using a REST API call - Cache the list of articles in an SQLite database - Download a thumbnail for each article - Cache the thumbnails to the filesystem - Build a complex UI such as a custom table view cell style All of the above are potentially long-running operations that impact user experience so it's important to benchmark and optimize them across various devices. Applications Manager Mobile APM gathers and aggregates metrics from all your users across the globe by embedding an APM agent in your applications in the form of a library. The APM agent measures the execution time of your code using transactions and components. In the previous example, the entire sequence of operations from starting navigation to rendering the final UI can be considered a transaction. The individual operations can be grouped into different component types such as HTTP, SQLite, filesystem, UI etc. Simple operations can be measured using just transactions, while complex operations can be measured using transactions with components. Browse through the following topics to learn more about configuring Mobile App Monitor for iOS: - [Integrate MEAPMInsight using Swift Package Manager](#integrate) - [Configure MEAPMInsight](#configure) - [Start a transaction](#start) - [Group operations within a transaction using components](#group) - [Flush the data](#flush) - [Custom APIs](#custom) ## Integrate MEAPMInsight using Swift Package Manager Follow the steps given below to integrate MEAPMInsight package from repository using Swift Package Manager: 1. Open your project in **Xcode**. 2. Select **File → Add Packages**… from the menu bar. 3. Enter the package repository URL: [https://github.com/ManageEngine/MEAPMInsight](https://github.com/ManageEngine/MEAPMInsight) 4. Set the **Dependency Rule** to **Up to Next Major Version.** 5. Select the **application target** where the package should be added. 6. Click **Add Package**. ## Configure MEAPMInsight After adding MEAPMInsight using Swift Package Manager, complete the following configuration steps: ### 1. Create a Mobile App Monitor - Create a [Mobile App Monitor](https://www.manageengine.com/products/applications_manager/help/configuring-mobile-app-monitor.html) in the MangeEngine Applications Manager. - Download the generated configuration (**apm_config.plist**) file. ### 2. Add configuration file to your Xcode project - Drag the downloaded configuration (**apm_config.plist**) file into the root of your Xcode project. - In the dialog: - Set **Action** to **Copy files to destination**. - Select the required **application target**. - Click **Finish**. ### 3. Self-signed certificate (if applicable) If your DEM Collector uses a self-signed SSL certificate: #### i. Add Certificate to Xcode Project: - Copy the apm_cert.cer from DEM Collector installed directory **/conf/sslcerts** and move it to the server were Xcode project is opened. - Drag **apm_cert.cer** into the root of your Xcode project. - In the dialog: - Set **Action** to **Copy files to destination**. - Select the required **application target**. - Click **Finish**. This allows the MEAPMInsight SDK to securely communicate with the DEM Collector. #### ii. Add App Transport Security Exception When using a self-signed certificate, you must allow an exception in **Info.plist** file for the DEM collector domain. To do so, open the **Info.plist** file and add the following configuration: ```xml NSAppTransportSecurity NSExceptionDomains [collectordomain].com NSExceptionAllowsInsecureHTTPLoads ``` Replace **[collectordomain].com** with your DEM collector domain. ### 4. Initialize the SDK in `AppDelegate` class Initialize the SDK in your `AppDelegate` class using the following code to start monitoring: ```swift import MEAPMInsight @main class AppDelegate: UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { #if DEBUG APMInsight.setDebugLoggingEnabled(true) APMInsight.setEnvironment("debug") #endif APMInsight.startMonitoring() // Below are Optional APMInsight.enableErrorReporting() // Pass array of URLs to ignore from HTTP tracking APMInsight.enableHttpTracking([ "https://api.example.com/analytics", "https://cdn.example.com" ]) return true } } ``` **Important:** To ensure crash reports are properly symbolicated, set **Debug Information Format** to **DWARF with dSYM file** in the Build Settings. Make sure this setting is applied to your release builds before distributing/shipping your app. ## Start a transaction You should typically start a transaction before a long-running operation and stop it when the operation is complete. Transactions are thread-safe and can be started and stopped from different threads. A transaction object can only be started and stopped once. Transactions with the same name are averaged across the application. Thus, when the same operation is executed multiple times using the same transaction name, the average execution time is recorded. ```swift func listArticles() { let transaction = APMInsight.startTransaction(withName: "List Articles") // Perform long-running operation APMInsight.stopTransaction(transaction) } ``` ## Group operations within a transaction using components You can group operations within a transaction into different types of components. You can use one of the predefined types such as APMHTTPComponent, APMSQLiteComponent, APMUIComponent etc., or specify your own type. Components are thread-safe and can be started and stopped from different threads. A component object can only be started and stopped once. A component object cannot be stopped after its parent transaction has already been stopped. Multiple components in a transaction can overlap and run in parallel. Components with the same name within a transaction are averaged. ```swift func listArticles() { let transaction = APMInsight.startTransaction(withName: "List Articles") let httpComponent = transaction?.startComponent(withType: APMHttpComponent) let articlesComponent = transaction?.startComponent(withType: "Download Articles") // Download articles transaction?.stopComponent(articlesComponent) for article in articles { let thumbnailComponent = transaction?.startComponent(withType: "Download Thumbnail") // Download thumbnail transaction?.stopComponent(thumbnailComponent) } transaction?.stopComponent(httpComponent) APMInsight.stopTransaction(transaction) } ``` In the above example, the total time taken by HTTP operations (downloading articles and thumbnails) are measured by APMHttpComponent and the time taken for just the articles is measured by "Download Articles". The time taken for each thumbnail is averaged and recorded by "Download Thumbnail" since it executes multiple times within a loop. ## Flush the data Sometimes it's desirable to manually flush recently recorded data. You may want to do this if you record transactions just before your application is terminated. If you've set a large upload interval (the default is 30 seconds), you should manually flush data whenever appropriate in case the application is terminated before the next upload interval. ```swift APMInsight.flush() ``` ## Custom APIs Custom APIs are used to set dynamic values for user IDs, track custom events, capture screens, and more. This document will explain the various types of custom APIs available and the syntax to use them. Following are the list of available APIs: 1. [Initializing the SDK](#initializeSDK) 2. [Custom User ID](#customUserID) 3. [Transactions](#transactions) 4. [HTTP Call](#httpCall) 5. [Screen](#screen) 6. [Environment](#environment) 7. [Flush](#flush1) 8. [Exclude HTTP Calls](#excludeHTTPCalls) 9. [Exclude Screens](#excludeScreens) ### 1. Initializing the SDK You can use the API below to initialize the SDK with a custom upload interval. By default, the data upload interval is set to 30 seconds. **Syntax for Swift:** ```swift APMInsight.startMonitoring() ``` **Syntax for Objective-C:** ```objectivec [APMInsight startMonitoring]; ``` ### 2. Custom User ID By default, a unique user ID is generated by the SDK. If you want to customize user IDs, you can do so by implementing the following syntax. This comes in handy when you want to track metrics or debug issues specific to a particular user. **Note:** Do not pass any Personally Identifiable Information (PII) such as email addresses, phone numbers, or names to this function. Use anonymized or non-sensitive identifiers only. **Syntax for Swift:** ```swift APMInsight.setUserId("user123") ``` **Syntax for Objective-C:** ```objectivec [APMInsight setUserId:@"user123"]; ``` ### 3. Transactions You can use the API below to track custom events with application-specific timing. **Syntax for Swift:** ```swift let transaction = APMInsight.startTransaction(withName: "List Articles") let httpComponent = transaction?.startComponent(withType: APMHttpComponent) let articlesComponent = transaction?.startComponent(withType: "Download Articles") // Download articles transaction?.stopComponent(articlesComponent) for article in articles { let thumbnailComponent = transaction?.startComponent(withType: "Download Thumbnail") // Download thumbnail transaction?.stopComponent(thumbnailComponent) } transaction?.stopComponent(httpComponent) APMInsight.stopTransaction(transaction) ``` **Syntax for Objective-C:** ```objectivec APMInsightTransaction *transaction = [APMInsight startTransactionWithName:@"List Articles"]; APMInsightComponent *httpComponent = [transaction startComponentWithType:APMHttpComponent]; APMInsightComponent *articlesComponent = [transaction startComponentWithType:@"Download Articles"]; // Download articles [transaction stopComponent:articlesComponent]; for (Article *article in articles) { APMInsightComponent *thumbnailComponent = [transaction startComponentWithType:@"Download Thumbnail"]; // Download thumbnail [transaction stopComponent:thumbnailComponent]; } [transaction stopComponent:httpComponent]; [APMInsight stopTransaction:transaction]; ``` ### 4. HTTP Call By default, network requests that use the following networking libraries are automatically captured. - *NSUrlSession* - *NSUrlConnection* If you want to add more HTTP calls, use the API below: **Syntax for Swift:** ```swift /*! Add a http call. This method is thread-safe. @param name The http call name. @param duration The http call load time in milliseconds. @param startTime The http call start time in milliseconds. @param respCode The http call response code. @param httpMethod The http call method. */ APMInsight.addHttpCall("example.com", withTime: 30, startTime:1642750130310, respCode: 200, httpMethod: "GET") ``` **Syntax for Objective-C:** ```objectivec /*! Add a http call. This method is thread-safe. @param name The http call name. @param duration The http call load time in milliseconds. @param startTime The http call start time in milliseconds. @param respCode The http call response code. @param httpMethod The http call method. */ [APMInsight addHttpCall:@"example.com" withTime:30 startTime: 1642750130310 respCode:200 httpMethod:@"GET"]; ``` ### 5. Screen By default, all screens are captured; however, if you want to add a missing screen, use the API given below. **Syntax for Swift:** ```swift /*! Add a screen. This method is thread-safe. @param screen The screen name. @param duration The screen load time. @param startTime The screen start time. */ APMInsight.addScreen("DetailScreen", withTime: 60, startTime: "1642750130310") ``` **Syntax for Objective-C:** ```objectivec /*! Add a screen. This method is thread-safe. @param screen The screen name. @param duration The screen load time. @param startTime The screen start time. */ [APMInsight addScreen:@"DetailScreen" withTime:60 startTime:@"1642750130310"]; ``` #### SwiftUI View Tracking By default, SwiftUI views are not tracked automatically. To track SwiftUI views, you need to manually add the `.trackScreen()` modifier to your views. **Syntax for swiftUI:** ```swift import SwiftUI struct MainView: View { var body: some View { NavigationView { VStack { NavigationLink("Go to Detail", destination: DetailView()) } .trackScreen("MainView") } } } struct DetailView: View { var body: some View { VStack { Text("Detail Screen") } .trackScreen("DetailView") } } ``` **Note:** - The `.trackScreen()` modifier should be added to each SwiftUI view you want to track. - The screen name parameter should be a descriptive string that identifies the view. ### 6. Environment You can use the API below to set custom environment details for filtering data across various environment setups like development, debug, production, or release. **Syntax for Swift:** ```swift /*! Sets the environment name for the APM agent. @param environment A custom environment name (for example, "debug", "release", or "beta-release") */ APMInsight.setEnvironment("release") ``` **Syntax for Objective-C:** ```objectivec /*! Sets the environment name for the APM agent. @param environment A custom environment name (for example, "debug", "release", or "beta-release") */ [APMInsight setEnvironment:@"release"]; ``` ### 7. Flush You can use the API below to immediately upload recorded data to the DEM Collector instead of waiting for the next upload interval. By default, the flush interval is set to 60 seconds. **Syntax for Swift:** ```swift APMInsight.flush() ``` **Syntax for Objective-C:** ```objectivec [APMInsight flush]; ``` **Note:** If there's no network available, data will be held in the queue until the next upload interval. If you've configured a long upload interval, you may need to manually flush data. ### 8. Exclude HTTP Calls You can exclude custom URLs from tracking by passing a list of URLs as an argument to the API below. **Syntax for Swift:** ```swift APMInsight.enableHttpTracking(["example.com", "demo.com"]) ``` **Syntax for Objective-C:** ```objectivec [APMInsight enableHttpTracking:@[@"example.com", @"demo.com"]]; ``` ### 9. Exclude Screens You can exclude custom screens from tracking by passing a list of screens as an argument to the API below. **Syntax for Swift:** ```swift APMInsight.ignoreScreens(["DetailScreen", "HomeScreen"]) ``` **Syntax for Objective-C:** ```objectivec [APMInsight ignoreScreens:@[@"DetailScreen", @"HomeScreen"]]; ```