Skip to content

iOS/tvOS Application Integration


Collect metrics data from various iOS applications and analyze the performance of each iOS application side through visualization.

Prerequisites

Note

If the RUM Headless service has been activated, the prerequisites are automatically configured, and you can directly integrate the application.

Application Integration

  1. Go to RUM > Create Application > iOS;
  2. Enter the application name;
  3. Enter the application ID;
  4. Choose the application integration method:

    • Public DataWay: Directly receives RUM data without installing the DataKit collector.
    • Local Environment Deployment: Receives RUM data after meeting the prerequisites.

Installation

tvOS

Source Code Repository: https://github.com/GuanceCloud/datakit-ios

Demo: https://github.com/GuanceDemo/guance-app-demo

  1. Configure the Podfile file.

    • Using Dynamic Library

      use_frameworks!
      def shared_pods
        pod 'FTMobileSDK', '[latest_version]'
        # If widget Extension data collection is needed
        pod 'FTMobileSDK', :subspecs => ['Extension'] 
      end
      
      # Main project
      target 'yourProjectName' do
        shared_pods
      end
      
      # Widget Extension
      target 'yourWidgetExtensionName' do
        shared_pods
      end
      

    • Using Static Library

      use_modular_headers!
      # Main project
      target 'yourProjectName' do
        pod 'FTMobileSDK', '[latest_version]'
      end
      # Widget Extension
      target 'yourWidgetExtensionName' do
        pod 'FTMobileSDK', :subspecs => ['Extension'] 
      end
      

    • Download the code repository to local and use it

      Podfile file:

      use_modular_headers!
      # Main project
      target 'yourProjectName' do
        pod 'FTMobileSDK', :path => '[folder_path]' 
      end
      # Widget Extension
      target 'yourWidgetExtensionName' do
        pod 'FTMobileSDK', :subspecs => ['Extension'] , :path => '[folder_path]'
      end
      

      folder_path: The path to the folder containing the FTMobileSDK.podspec file.

      FTMobileSDK.podspec file:

      Modify s.version and s.source in the FTMobileSDK.podspec file.

      Pod::Spec.new do |s|
        s.name         = "FTMobileSDK"
        s.version      = "[latest_version]"  
        s.source       = { :git => "https://github.com/GuanceCloud/datakit-ios.git", :tag => s.version }
      end
      

      s.version: Modify to the specified version, it is recommended to be consistent with SDK_VERSION in FTMobileSDK/FTMobileAgent/Core/FTMobileAgentVersion.h.

      s.source: tag => s.version

  2. Execute pod install in the Podfile directory to install the SDK.

  1. Configure the Cartfile file.

    github "GuanceCloud/datakit-ios" == [latest_version]
    

  2. Update dependencies.

    Depending on your target platform (iOS or tvOS), execute the corresponding carthage update command and add the --use-xcframeworks parameter to generate XCFrameworks:

    • For iOS platform:

      carthage update --platform iOS --use-xcframeworks
      

    • For tvOS platform:

      carthage update --platform tvOS --use-xcframeworks
      

    The generated xcframework is used in the same way as a regular Framework. Add the compiled library to the project.

    FTMobileAgent: Add to the main project Target, supports iOS and tvOS platforms.

    FTMobileExtension: Add to the Widget Extension Target.

  3. Add -ObjC to TARGETS -> Build Setting -> Other Linker Flags.

  4. When integrating with Carthage, SDK version support:

    FTMobileAgent: >=1.3.4-beta.2

    FTMobileExtension: >=1.4.0-beta.1

Using Xcode UI

  1. Select PROJECT -> Package Dependency, click the + under the Packages section.

  2. In the pop-up page's search box, enter https://github.com/GuanceCloud/datakit-ios.git.

  3. After Xcode successfully fetches the package, the SDK configuration page will be displayed.

    Dependency Rule: It is recommended to select Up to Next Major Version.

    Add To Project: Select the supported project.

    After filling in the configuration, click the Add Package button and wait for the loading to complete.

  4. In the pop-up window Choose Package Products for datakit-ios, select the Target to which the SDK needs to be added, click the Add Package button, and the SDK is now successfully added.

    FTMobileSDK: Add to the main project Target.

    FTMobileExtension: Add to the Widget Extension Target.

Using Package.swift

If your project is managed by SPM, add the SDK as a dependency by adding dependencies to Package.swift.

// Main project
 dependencies: [
     .package(url: "https://github.com/GuanceCloud/datakit-ios.git", 
              .upToNextMajor(from: "[latest_version]"))
    ]    

Add dependencies for your Targets.

targets: [
    .target(
        name: "YourTarget",
        dependencies: [
            .product(name: "FTMobileSDK", package: "FTMobileSDK"),
        ]),
    .target(
        name: "YourWidgetExtensionTarget",
        dependencies: [
            .product(name: "FTMobileExtension", package: "FTMobileSDK"),
        ]),
    ]

Note: Swift Package Manager is supported from version 1.4.0-beta.1 and above.

Add Header File

//CocoaPods, SPM 
#import "FTMobileSDK.h"
//Carthage 
#import <FTMobileSDK/FTMobileSDK.h>
import FTMobileSDK

SDK Initialization

Basic Configuration

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    // SDK FTMobileConfig settings
     // Local environment deployment, Datakit deployment
     //FTMobileConfig *config = [[FTMobileConfig alloc]initWithDatakitUrl:datakitUrl];
     // Use public DataWay deployment
    FTMobileConfig *config = [[FTMobileConfig alloc]initWithDatawayUrl:datawayUrl clientToken:clientToken];
    //config.enableSDKDebugLog = YES;              //debug mode
    config.compressIntakeRequests = YES;
    //Start SDK
    [FTMobileAgent startWithConfigOptions:config];

   //...
    return YES;
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
     // SDK FTMobileConfig settings
       // Local environment deployment, Datakit deployment
       //let config = FTMobileConfig(datakitUrl: url)
       // Use public DataWay deployment
     let config = FTMobileConfig(datawayUrl: datawayUrl, clientToken: clientToken)
     //config.enableSDKDebugLog = true              //debug mode
     config.compressIntakeRequests = true           //Compress intake data
     FTMobileAgent.start(withConfigOptions: config)
     //...
     return true
}
Attribute Type Required Meaning
datakitUrl NSString Yes Local environment deployment (Datakit) reporting URL address, example: http://10.0.0.1:9529, default port 9529, the device installing the SDK must be able to access this address. Note: Choose one between datakitUrl and datawayUrl configuration
datawayUrl NSString Yes Public DataWay reporting URL address, obtained from the [RUM] application, example: https://open.dataway.url, the device installing the SDK must be able to access this address. Note: Choose one between datakitUrl and datawayUrl configuration
clientToken NSString Yes Authentication token, needs to be used together with datawayUrl.
enableSDKDebugLog BOOL No Set whether to allow printing logs. Default NO.
env NSString No Set the collection environment. Default prod, supports customization, can also be set using the provided FTEnv enumeration via the -setEnvWithType: method.
service NSString No Set the name of the business or service. Affects the service field data in Log and RUM. Default: df_rum_ios.
globalContext NSDictionary No Add custom tags. Addition rules please refer to here.
groupIdentifiers NSArray No Array of AppGroups Identifiers corresponding to Widget Extensions that need to be collected. If Widget Extensions data collection is enabled, App Groups must be set, and the Identifier must be configured in this attribute.
autoSync BOOL No Whether to automatically sync data to the server after collection. Default YES. When NO, use [[FTMobileAgent sharedInstance] flushSyncData] to manage data synchronization manually.
syncPageSize int No Set the number of entries per sync request. Range [5,). Note: A larger number of request entries means data synchronization occupies more computing resources. Default is 10.
syncSleepTime int No Set the sync interval time. Range [0,5000], default is not set.
enableDataIntegerCompatible BOOL No It is recommended to enable when coexisting with web data. This configuration is used to handle web data type storage compatibility issues.
compressIntakeRequests BOOL No Deflate compression for uploaded sync data. This parameter is supported from SDK version 1.5.6 and above, default is off.
enableLimitWithDbSize BOOL No Enable the function to limit total cache size using DB.
Note: After enabling, FTLoggerConfig.logCacheLimitCount and FTRUMConfig.rumCacheLimitCount will become invalid. This parameter is supported from SDK version 1.5.8 and above.
dbCacheLimit long No DB cache limit size. Range [30MB,), default 100MB, unit byte. Supported from SDK version 1.5.8 and above.
dbDiscardType FTDBCacheDiscard No Set the data discard rule in the database. Default FTDBDiscard
FTDBDiscard: When the data count exceeds the maximum, discard appended data. FTDBDiscardOldest: When the data count exceeds the maximum, discard the oldest data. Supported from SDK version 1.5.8 and above.
dataModifier FTDataModifier No Modify individual fields. Supported from SDK version 1.5.16 and above. Usage example can be found here.
lineDataModifier FTLineDataModifier No Modify single data entries. Supported from SDK version 1.5.16 and above. Usage example can be found here.
remoteConfiguration BOOL No Whether to enable the remote configuration function for data collection. Default is not enabled. After enabling, SDK initialization or application hot start will trigger data updates. Supported from SDK version 1.5.17 and above. Datakit version requirement >=1.60 or using public dataway.
remoteConfigMiniUpdateInterval int No Set the minimum update interval for remote dynamic configuration, unit seconds, default 12 hours. Supported from SDK version 1.5.17 and above.
remoteConfigFetchCompletionBlock FTRemoteConfigFetchCompletionBlock No Remote configuration result callback, used to receive fetch results and support custom adjustment of the configuration model. Supported from SDK version 1.5.19 and above. Usage example can be found here.

RUM Configuration

    //Enable rum
    FTRumConfig *rumConfig = [[FTRumConfig alloc]initWithAppid:appid];
    rumConfig.enableTraceUserView = YES;
    rumConfig.deviceMetricsMonitorType = FTDeviceMetricsMonitorAll;
    rumConfig.monitorFrequency = FTMonitorFrequencyRare;
    rumConfig.enableTraceUserAction = YES;
    rumConfig.enableTraceUserResource = YES;
    rumConfig.enableTrackAppFreeze = YES;
    rumConfig.enableTrackAppCrash = YES;
    rumConfig.enableTrackAppANR = YES;
    rumConfig.errorMonitorType = FTErrorMonitorAll;
    [[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
    let rumConfig = FTRumConfig(appid: appid)
    rumConfig.enableTraceUserView = true
    rumConfig.deviceMetricsMonitorType = .all
    rumConfig.monitorFrequency = .rare
    rumConfig.enableTraceUserAction = true
    rumConfig.enableTraceUserResource = true
    rumConfig.enableTrackAppFreeze = true
    rumConfig.enableTrackAppCrash = true
    rumConfig.enableTrackAppANR = true
    rumConfig.errorMonitorType = .all
    FTMobileAgent.sharedInstance().startRum(withConfigOptions: rumConfig)
Attribute Type Required Meaning
appid NSString Yes RUM application ID unique identifier. Corresponds to setting the RUM appid, which enables the RUM collection function. Method to obtain appid.
samplerate int No Sampling rate. Value range [0,100], 0 means no collection, 100 means full collection, default value is 100. Scope is all View, Action, LongTask, Error data under the same session_id.
sessionOnErrorSampleRate int No Set error collection rate. When a session is not sampled by samplerate, if an error occurs during the session, data from 1 minute before the error can be collected. Value range [0,100], 0 means no collection, 100 means full collection, default value is 0. Scope is all View, Action, LongTask, Error data under the same session_id. Supported from SDK version 1.5.16 and above.
enableTrackAppCrash BOOL No Set whether to collect crash logs. Default NO.
enableTrackAppANR BOOL No Collect ANR (Application Not Responding) freeze events. Default NO.
enableTrackAppFreeze BOOL No Collect UI freeze events. Default NO
Can enable freeze collection and set the freeze threshold via the -setEnableTrackAppFreeze:freezeDurationMs: method.
freezeDurationMs long No Set the UI freeze threshold. Value range [100,), unit milliseconds, default 250ms. Supported from SDK version 1.5.7 and above.
enableTraceUserView BOOL No Set whether to track user View operations. Default NO.
enableTraceUserAction BOOL No Set whether to track user Action operations. Default NO
Can customize action_name via view.accessibilityIdentifier.
enableTraceUserResource BOOL No Set whether to track user network requests. Default NO, only affects native http.
Note: Network requests initiated via [NSURLSession sharedSession] cannot collect performance data;
SDK 1.5.9 and above support collecting network requests initiated via Swift's URLSession async/await APIs.
resourceUrlHandler FTResourceUrlHandler No Custom collection resource rule. Default is no filtering. Return: NO means to collect, YES means not to collect.
errorMonitorType FTErrorMonitorType No Error event monitoring supplement type. Adds monitored information to the collected crash data. FTErrorMonitorBattery for battery level, FTErrorMonitorMemory for memory usage, FTErrorMonitorCpu for CPU usage, default is not set.
deviceMetricsMonitorType FTDeviceMetricsMonitorType No View performance monitoring type, default is not set. Adds corresponding monitoring item information to the collected View data. FTDeviceMetricsMonitorMemory monitors current application memory usage, FTDeviceMetricsMonitorCpu monitors CPU ticks, FTDeviceMetricsMonitorFps monitors screen frame rate, default is not set.
monitorFrequency FTMonitorFrequency No View performance monitoring sampling period. Configure monitorFrequency to set the sampling period for View monitoring item information. FTMonitorFrequencyDefault 500ms (default), FTMonitorFrequencyFrequent 100ms, FTMonitorFrequencyRare 1000ms.
enableResourceHostIP BOOL No Whether to collect the IP address of the request target domain. Supported under >= iOS 13.0 >= tvOS 13.0.
globalContext NSDictionary No Add custom tags for user monitoring data source differentiation. If tracking function is needed, the parameter key should be track_id, value can be any value. For addition rule precautions, please refer to here.
rumCacheLimitCount int No RUM maximum cache count. Default 100_000. Supported from SDK version 1.5.8 and above.
rumDiscardType FTRUMCacheDiscard No Set RUM discard rule. Default FTRUMCacheDiscard
FTRUMCacheDiscard: When RUM data count exceeds the maximum, discard appended data. FTRUMDiscardOldest: When RUM data count exceeds the maximum, discard the oldest data. Supported from SDK version 1.5.8 and above.
resourcePropertyProvider FTResourcePropertyProvider No Add RUM Resource custom attributes via block callback. Supported from SDK version 1.5.10 and above. Priority is lower than URLSession custom collection.
enableTraceWebView BOOL No Set to enable webView data collection, default YES. Supported from SDK version 1.5.17 and above.
allowWebViewHost NSArray No Set the allowed WebView host addresses for data tracking. nil means collect all, default is nil. Supported from SDK version 1.5.17 and above.
sessionTaskErrorFilter FTSessionTaskErrorFilter No Set whether to intercept URLSessionTask Error. Return YES to confirm interception, NO to not intercept. After interception, RUM-Error will not collect this error. Supported from SDK version 1.5.17 and above.
viewTrackingHandler FTViewTrackingHandler No Custom View tracking logic, used to determine which ViewControllers need to be monitored as RUM Views and customize View Name.
Effective condition: enableTraceUserView = YES. Supported from SDK version 1.5.18 and above. Usage example can be found here.
actionTrackingHandler FTActionTrackingHandler No Custom Action tracking logic, used to filter RUM Action events that need to be recorded and customize Action Name.
Effective condition: enableTraceUserAction = YES. Supported from SDK version 1.5.18 and above. Usage example can be found here.
crashMonitoring FTCrashMonitorType No Configure the type range of SDK crash monitoring. Default is FTCrashMonitorTypeHighCompatibility (high compatibility mode preset macro).
Effective condition: enableTrackAppCrash = YES
Note: It is necessary to specify FTCrashMonitorTypeSystem |FTCrashMonitorTypeApplicationState, as these provide important information for reports.
Supported from SDK version 1.5.19 and above.

Log Configuration

    //Enable logger
    FTLoggerConfig *loggerConfig = [[FTLoggerConfig alloc]init];
    loggerConfig.enableCustomLog = YES;
    loggerConfig.enableLinkRumData = YES;
    loggerConfig.logLevelFilter = @[@(FTStatusError),@(FTStatusCritical)];
    loggerConfig.discardType = FTDiscardOldest;
    [[FTMobileAgent sharedInstance] startLoggerWithConfigOptions:loggerConfig];
    let loggerConfig = FTLoggerConfig()
    loggerConfig.enableCustomLog = true
    loggerConfig.enableLinkRumData = true
    loggerConfig.logLevelFilter = [NSNumber(value: FTLogStatus.statusError.rawValue),NSNumber(value: FTLogStatus.statusCritical.rawValue)] // loggerConfig.logLevelFilter = [2,3]
    loggerConfig.discardType = .discardOldest
    FTMobileAgent.sharedInstance().startLogger(withConfigOptions: loggerConfig)
Attribute Type Required Meaning
samplerate int No Sampling rate. Value range [0,100], 0 means no collection, 100 means full collection, default value is 100.
enableCustomLog BOOL No Whether to upload custom logs. Default NO.
printCustomLogToConsole BOOL No Set whether to output custom logs to the console. Default NO. Custom log output format.
logLevelFilter NSArray No Set log level filtering, default is not set. Example:
1. To collect logs with levels Info and Error, set to @[@(FTStatusInfo),@(FTStatusError)] or @[@0,@1]
2. To collect logs including custom levels, e.g., collect "customLevel" and Error, set to @[@"customLevel",@(FTStatusError)]
Supported from SDK version 1.5.16 and above for filtering custom levels.
enableLinkRumData BOOL No Whether to associate with RUM data. Default NO.
globalContext NSDictionary No Add log custom tags. Addition rules please refer to here.
logCacheLimitCount int No Local cache maximum log entry count limit [1000,). The larger the log, the greater the disk cache pressure. Default is 5000.
discardType FTLogCacheDiscard No Set the log discard rule after reaching the limit. Default FTDiscard
FTDiscard: When log data count exceeds the maximum (5000), discard appended data. FTDiscardOldest: When log data count exceeds the maximum, discard the oldest data.

Trace Configuration

   //Enable trace
   FTTraceConfig *traceConfig = [[FTTraceConfig alloc]init];
   traceConfig.enableLinkRumData = YES;
     traceConfig.enableAutoTrace = YES;
   traceConfig.networkTraceType = FTNetworkTraceTypeDDtrace;
   [[FTMobileAgent sharedInstance] startTraceWithConfigOptions:traceConfig];
   let traceConfig = FTTraceConfig.init()
   traceConfig.enableLinkRumData = true
   traceConfig.enableAutoTrace = true
   FTMobileAgent.sharedInstance().startTrace(withConfigOptions: traceConfig)
Attribute Type Required Meaning
samplerate int No Sampling rate. Value range [0,100], 0 means no collection, 100 means full collection, default value is 100.
networkTraceType FTNetworkTraceType No Set the type of link tracing. Default is DDTrace. Currently supports Zipkin, Jaeger, DDTrace, Skywalking (8.0+), TraceParent (W3C). If integrating with OpenTelemetry, please check the supported types and agent related configurations when choosing the corresponding link type.
enableLinkRumData BOOL No Whether to associate with RUM data. Default NO.
enableAutoTrace BOOL No Set whether to enable automatic http trace. Default NO, currently only supports NSURLSession.
traceInterceptor FTTraceInterceptor No Support custom link tracing judgment via URLRequest. Return TraceContext after confirming interception, return nil if not intercepted. Supported from SDK version 1.5.10 and above. Priority is lower than URLSession custom collection.

RUM User Data Tracking

Use FTRUMConfig to configure enableTraceUserAction, enableTraceUserView, enableTraceUserResource, enableTrackAppFreeze, enableTrackAppCrash, and enableTrackAppANR to achieve automatic collection and tracking of Action, View, Resource, LongTask, and Error data. If you want custom collection, you can use FTExternalDataManager to report data, as shown in the following examples:

View

Usage

/// Create a page
///
/// Call this method before `-startViewWithName`. This method is used to record the page loading time. If the loading time cannot be obtained, this method can be omitted.
/// - Parameters:
///  - viewName: Page name
///  - loadTime: Page loading time (nanoseconds)
-(void)onCreateView:(NSString *)viewName loadTime:(NSNumber *)loadTime;

/// Enter page
/// - Parameters:
///  - viewName: Page name
///  - property: Event custom attributes (optional)
-(void)startViewWithName:(NSString *)viewName property:(nullable NSDictionary *)property;

/// Update the loading time of the current RUM View.
/// Must be called between `-startView` and `-stopView` methods to take effect.
/// - Parameter duration: Loading duration (nanoseconds).
-(void)updateViewLoadingTime:(NSNumber *)duration;

/// Leave page
/// - Parameter property: Event custom attributes (optional)
-(void)stopViewWithProperty:(nullable NSDictionary *)property;
/// Create a page
///
/// Call this method before `-startViewWithName`. This method is used to record the page loading time. If the loading time cannot be obtained, this method can be omitted.
/// - Parameters:
///  - viewName: Page name
///  - loadTime: Page loading time (ns)
open func onCreateView(_ viewName: String, loadTime: NSNumber)

/// Enter page
/// - Parameters:
///  - viewName: Page name
///  - property: Event custom attributes (optional)
open func startView(withName viewName: String, property: [AnyHashable : Any]?)

/// Update the loading time of the current RUM View.
/// Must be called between `-startView` and `-stopView` methods to take effect.
/// - Parameter duration: Loading duration (nanoseconds).
open func updateViewLoadingTime(_ duration: NSNumber)

/// Leave page
/// - Parameter property: Event custom attributes (optional)
open func stopView(withProperty property: [AnyHashable : Any]?)

Code Example

- (void)viewDidAppear:(BOOL)animated{
  [super viewDidAppear:animated];
  // Scenario 1:
  [[FTExternalDataManager sharedManager] startViewWithName:@"TestVC"];  

  // Scenario 2: Dynamic parameters
  [[FTExternalDataManager sharedManager] startViewWithName:@"TestVC" property:@{@"custom_key":@"custom_value"}];  
}
-(void)viewDidDisappear:(BOOL)animated{
  [super viewDidDisappear:animated];
  // Scenario 1:
  [[FTExternalDataManager sharedManager] stopView];  

  // Scenario 2: Dynamic parameters
  [[FTExternalDataManager sharedManager] stopViewWithProperty:@{@"custom_key":@"custom_value"}];
}
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Scenario 1:
    FTExternalDataManager.shared().startView(withName: "TestVC")
    // Scenario 2: Dynamic parameters
    FTExternalDataManager.shared().startView(withName: "TestVC",property: ["custom_key":"custom_value"])
}
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    // Scenario 1:
    FTExternalDataManager.shared().stopView()
    // Scenario 2: Dynamic parameters
    FTExternalDataManager.shared().stopView(withProperty: ["custom_key":"custom_value"])
}

Action

Usage

/// Start RUM Action.
///
/// RUM will bind Resource, Error, LongTask events that this Action may trigger. Avoid adding multiple times within 0.1 s. Only one Action can be associated with the same View at the same time. If the previous Action has not ended, the new Action will be discarded.
/// Does not interfere with Actions added via the `addAction:actionType:property` method.
///
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom attributes (optional)
- (void)startAction:(NSString *)actionName actionType:(NSString *)actionType property:(nullable NSDictionary *)property;

/// Add Action event. No duration, no discard logic.
///
/// Does not interfere with RUM Actions started via `startAction:actionType:property:`.
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom attributes (optional)
- (void)addAction:(NSString *)actionName actionType:(NSString *)actionType property:(nullable NSDictionary *)property;
/// Start RUM Action.
///
/// RUM will bind Resource, Error, LongTask events that this Action may trigger. Avoid adding multiple times within 0.1 s. Only one Action can be associated with the same View at the same time. If the previous Action has not ended, the new Action will be discarded.
/// Does not interfere with Actions added via the `addAction:actionType:property` method.
///
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom attributes (optional)
open func startAction(_ actionName: String, actionType: String, property: [AnyHashable : Any]?)

/// Add Action event. No duration, no discard logic.
///
/// Does not interfere with RUM Actions started via `startAction:actionType:property:`.
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom attributes (optional)
open func addAction(_ actionName: String, actionType: String, property: [AnyHashable : Any]?)

Code Example

// startAction
[[FTExternalDataManager sharedManager] startAction:@"action" actionType:@"click" property:@{@"action_property":@"testActionProperty1"}];
// addAction
[[FTExternalDataManager sharedManager] addAction:@"action" actionType:@"click" property:@{@"action_property":@"testActionProperty1"}];
// startAction
FTExternalDataManager.shared().startAction("custom_action", actionType: "click",property: nil)
// addAction
FTExternalDataManager.shared().addAction("custom_action", actionType: "click",property: nil)

Error

Usage

/// Add Error event
/// - Parameters:
///   - type: error type
///   - message: Error message
///   - stack: Stack information
///   - property: Event custom attributes (optional)
- (void)addErrorWithType:(NSString *)type message:(NSString *)message stack:(NSString *)stack property:(nullable NSDictionary *)property;

/// Add Error event
/// - Parameters:
///   - type: error type
///   - state: Program running state
///   - message: Error message
///   - stack: Stack information
///   - property: Event custom attributes (optional)
- (void)addErrorWithType:(NSString *)type state:(FTAppState)state  message:(NSString *)message stack:(NSString *)stack property:(nullable NSDictionary *)property;
/// Add Error event
/// - Parameters:
///   - type: error type
///   - message: Error message
///   - stack: Stack information
///   - property: Event custom attributes (optional)
open func addError(withType: String, message: String, stack: String, property: [AnyHashable : Any]?)

/// Add Error event
/// - Parameters:
///   - type: error type
///   - state: Program running state
///   - message: Error message
///   - stack: Stack information
///   - property: Event custom attributes (optional)
open func addError(withType type: String, state: FTAppState, message: String, stack: String, property: [AnyHashable : Any]?)

Code Example

// Scenario 1
[[FTExternalDataManager sharedManager] addErrorWithType:@"type" message:@"message" stack:@"stack"];
// Scenario 2: Dynamic parameters
[[FTExternalDataManager sharedManager] addErrorWithType:@"ios_crash" message:@"crash_message" stack:@"crash_stack" property:@{@"custom_key":@"custom_value"}];
// Scenario 3: Dynamic parameters
[[FTExternalDataManager sharedManager] addErrorWithType:@"ios_crash" state:FTAppStateUnknown message:@"crash_message" stack:@"crash_stack" property:@{@"custom_key":@"custom_value"}];
// Scenario 1
FTExternalDataManager.shared().addError(withType: "custom_type", message: "custom_message", stack: "custom_stack")
// Scenario 2: Dynamic parameters
FTExternalDataManager.shared().addError(withType: "custom_type", message: "custom_message", stack: "custom_stack",property: ["custom_key":"custom_value"])
// Scenario 3: Dynamic parameters       
FTExternalDataManager.shared().addError(withType: "custom_type", state: .unknown, message: "custom_message", stack: "custom_stack", property: ["custom_key":"custom_value"])

LongTask

Usage

/// Add Freeze event
/// - Parameters:
///   - stack: Freeze stack
///   - duration: Freeze duration (nanoseconds)
///   - property: Event custom attributes (optional)
- (void)addLongTaskWithStack:(NSString *)stack duration:(NSNumber *)duration property:(nullable NSDictionary *)property;
/// Add Freeze event
/// - Parameters:
///   - stack: Freeze stack
///   - duration: Freeze duration (nanoseconds)
///   - property: Event custom attributes (optional)
func addLongTask(withStack: String, duration: NSNumber, property: [AnyHashable : Any]?)

Code Example

// Scenario 1
[[FTExternalDataManager sharedManager] addLongTaskWithStack:@"stack string" duration:@1000000000];
// Scenario 2: Dynamic parameters
[[FTExternalDataManager sharedManager] addLongTaskWithStack:@"stack string" duration:@1000000000 property:@{@"custom_key":@"custom_value"}];
// Scenario 1
FTExternalDataManager.shared().addLongTask(withStack: "stack string", duration: 1000000000)
// Scenario 2: Dynamic parameters
FTExternalDataManager.shared().addLongTask(withStack: "stack string", duration: 1000000000 ,property: [["custom_key":"custom_value"]])

Resource

Usage

/// HTTP request start
/// - Parameters:
///   - key: Request identifier
///   - property: Event custom attributes (optional)
- (void)startResourceWithKey:(NSString *)key property:(nullable NSDictionary *)property;

/// HTTP add request data
///
/// - Parameters:
///   - key: Request identifier
///   - metrics: Request-related performance attributes
///   - content: Request-related data
- (void)addResourceWithKey:(NSString *)key metrics:(nullable FTResourceMetricsModel *)metrics content:(FTResourceContentModel *)content;

/// HTTP request end
/// - Parameters:
///   - key: Request identifier
///   - property: Event custom attributes (optional)
- (void)stopResourceWithKey:(NSString *)key property:(nullable NSDictionary *)property;
/// HTTP request start
/// - Parameters:
///   - key: Request identifier
///   - property: Event custom attributes (optional)
open func startResource(withKey key: String, property: [AnyHashable : Any]?)

/// HTTP request end
/// - Parameters:
///   - key: Request identifier
///   - property: Event custom attributes (optional)
open func stopResource(withKey key: String, property: [AnyHashable : Any]?)

/// HTTP add request data
///
/// - Parameters:
///   - key: Request identifier
///   - metrics: Request-related performance attributes
///   - content: Request-related data
open func addResource(withKey key: String, metrics: FTResourceMetricsModel?, content: FTResourceContentModel)

Code Example

//Step 1: Before request starts
[[FTExternalDataManager sharedManager] startResourceWithKey:key];

//Step 2: Request completes
[[FTExternalDataManager sharedManager] stopResourceWithKey:key];

//Step 3: Assemble Resource data
//FTResourceContentModel data
FTResourceContentModel *content = [[FTResourceContentModel alloc]init];
content.httpMethod = request.HTTPMethod;
content.requestHeader = request.allHTTPHeaderFields;
content.responseHeader = httpResponse.allHeaderFields;
content.httpStatusCode = httpResponse.statusCode;
content.responseBody = responseBody;
//ios native
content.error = error;

//If time data for each phase can be obtained 
//FTResourceMetricsModel
//ios native obtained NSURLSessionTaskMetrics data, directly use FTResourceMetricsModel's initialization method
FTResourceMetricsModel *metricsModel = [[FTResourceMetricsModel alloc]initWithTaskMetrics:metrics];

//Other platforms, all time data in nanoseconds
FTResourceMetricsModel *metricsModel = [[FTResourceMetricsModel alloc]init];

//Step 4: add resource. If no time data, pass nil for metrics
[[FTExternalDataManager sharedManager] addResourceWithKey:key metrics:metricsModel content:content];
//Step 1: Before request starts
FTExternalDataManager.shared().startResource(withKey: key)

//Step 2: Request completes
FTExternalDataManager.shared().stopResource(withKey: resource.key)

//Step 3: ① Assemble Resource data
let contentModel = FTResourceContentModel(request: task.currentRequest!, response: task.response as? HTTPURLResponse, data: resource.data, error: error)

//② If time data for each phase can be obtained 
//FTResourceMetricsModel
//ios native obtained NSURLSessionTaskMetrics data, directly use FTResourceMetricsModel's initialization method
var metricsModel:FTResourceMetricsModel?
if let metrics = resource.metrics {
   metricsModel = FTResourceMetricsModel(taskMetrics:metrics)
}
//Other platforms, all time data in nanoseconds
metricsModel = FTResourceMetricsModel()
...

//Step 4: add resource. If no time data, pass nil for metrics
FTExternalDataManager.shared().addResource(withKey: resource.key, metrics: metricsModel, content: contentModel)

Logger Log Printing

In the SDK initialization Log configuration, configure enableCustomLog to allow custom log addition.

Currently, the log content is limited to 30 KB. Characters beyond this limit will be truncated.

Usage

//
//  FTLogger.h
//  FTMobileSDK

/// Add info level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
-(void)info:(NSString *)content property:(nullable NSDictionary *)property;

/// Add warning level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
-(void)warning:(NSString *)content property:(nullable NSDictionary *)property;

/// Add error level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
-(void)error:(NSString *)content  property:(nullable NSDictionary *)property;

/// Add critical level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
-(void)critical:(NSString *)content property:(nullable NSDictionary *)property;

/// Add ok level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
-(void)ok:(NSString *)content property:(nullable NSDictionary *)property;

/// Add custom log
/// - Parameters:
///   - content: Log content
///   - status: Log status level
///   - property: Custom attributes (optional)
- (void)log:(NSString *)content status:(NSString *)status property:(nullable NSDictionary *)property;
open class FTLogger : NSObject, FTLoggerProtocol {}
public protocol FTLoggerProtocol : NSObjectProtocol {
/// Add info level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
optional func info(_ content: String, property: [AnyHashable : Any]?)

/// Add warning level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
optional func warning(_ content: String, property: [AnyHashable : Any]?)

/// Add error level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
optional func error(_ content: String, property: [AnyHashable : Any]?)

/// Add critical level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
optional func critical(_ content: String, property: [AnyHashable : Any]?)

/// Add ok level custom log
/// - Parameters:
///   - content: Log content
///   - property: Custom attributes (optional)
optional func ok(_ content: String, property: [AnyHashable : Any]?)

/// Add custom log
/// - Parameters:
///   - content: Log content
///   - status: Log status level
///   - property: Custom attributes (optional)
optional func log(_ content: String, status: String, property: [AnyHashable : Any]?)
}

Log Levels

///Event level and status, default: FTStatusInfo
typedef NS_ENUM(NSInteger, FTLogStatus) {
    /// Info
    FTStatusInfo         = 0,
    /// Warning
    FTStatusWarning,
    /// Error
    FTStatusError,
    /// Critical
    FTStatusCritical,
    /// Ok
    FTStatusOk,
};
///Event level and status, default: FTStatusInfo
public enum FTLogStatus : Int, @unchecked Sendable {
    /// Info
    case statusInfo = 0
    /// Warning
    case statusWarning = 1
    /// Error
    case statusError = 2
    /// Critical
    case statusCritical = 3
    /// Ok
    case statusOk = 4
}

Code Example

// If the SDK is not initialized successfully, adding custom logs will fail.
[[FTLogger sharedInstance] info:@"test" property:@{@"custom_key":@"custom_value"}];
// If the SDK is not initialized successfully, adding custom logs will fail.
FTLogger.shared().info("contentStr", property: ["custom_key":"custom_value"])

Custom Log Output to Console

Set printCustomLogToConsole = YES to enable outputting custom logs to the console. You will see logs in the following format in the xcode debug console:

2023-06-29 13:47:56.960021+0800 App[64731:44595791] [IOS APP] [INFO] content ,{K=V,...,Kn=Vn}

2023-06-29 13:47:56.960021+0800 App[64731:44595791]: Standard prefix for os_log output;

[IOS APP]: Prefix to distinguish SDK output custom logs;

[INFO]: Custom log level;

content: Custom log content;

{K=V,...,Kn=Vn}: Custom attributes.

You can configure FTTraceConfig to enable automatic mode, and also support users to custom add Trace related data. The API for custom addition is as follows:

NSString *key = [[NSUUID UUID]UUIDString];
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
//Manual operation required: Obtain traceHeader before the request and add it to the request header.
NSDictionary *traceHeader = [[FTTraceManager sharedInstance] getTraceHeaderWithKey:key url:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (traceHeader && traceHeader.allKeys.count>0) {
    [traceHeader enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        [request setValue:value forHTTPHeaderField:field];
    }];
}
NSURLSession *session=[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
   //Your code
}];

[task resume];
let url:URL = NSURL.init(string: "https://www.baidu.com")! as URL
if let traceHeader = FTExternalDataManager.shared().getTraceHeader(withKey: NSUUID().uuidString, url: url) {
     let request = NSMutableURLRequest(url: url)
     //Manual operation required: Obtain traceHeader before the request and add it to the request header.
     for (a,b) in traceHeader {
         request.setValue(b as? String, forHTTPHeaderField: a as! String)
     }
     let task = URLSession.shared.dataTask(with: request as URLRequest) {  data,  response,  error in
        //Your code
     }
     task.resume()
}

Custom Collection of Network via Forwarding URLSession Delegate

The SDK provides a class FTURLSessionDelegate, which can be used to perform custom RUM Resource collection and link tracing for network requests initiated by a specific URLSession.

  • FTURLSessionDelegate supports intercepting URLResquest by setting the traceInterceptor block for custom link tracing (supported from SDK version 1.5.9 and above). Priority > FTTraceConfig.traceInterceptor.
  • FTURLSessionDelegate supports custom RUM Resource attributes that need additional collection via the provider block. Priority > FTRumConfig.resourcePropertyProvider.
  • FTURLSessionDelegate supports custom interception of SessionTask Error by setting the errorFilter block. (Supported from SDK version 1.5.17 and above)
  • return YES: Intercept, RUM-Error will not add this network_error.
  • return NO: Do not intercept, RUM-Error will add this network_error.
  • When used together with FTRumConfig.enableTraceUserResource and FTTraceConfig.enableAutoTrace, the priority is: Custom > Automatic Collection.

Three methods are provided below to meet different user scenarios.

Method One

Directly set the delegate object of the URLSession to an instance of FTURLSessionDelegate.

id<NSURLSessionDelegate> delegate = [[FTURLSessionDelegate alloc]init];
// Add custom RUM resource attributes. It is recommended to add the project abbreviation prefix to tag names, e.g., `df_tag_name`.
delegate.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
                NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
                return @{@"df_requestbody":body};
            };
// Support custom trace. Return TraceContext after confirming interception, return nil if not intercepted.
delegate.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
        FTTraceContext *context = [FTTraceContext new];
        context.traceHeader = @{@"trace_key":@"trace_value"};
        context.traceId = @"trace_id";
        context.spanId = @"span_id";
        return context;
    };  
// Custom whether to intercept SessionTask Error. return YES: intercept, RUM-Error will not add this `network_error`.   
delegate.errorFilter = ^BOOL(NSError * _Nonnull error) {
        return error.code == NSURLErrorCancelled;
    };    
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:nil];
let delegate = FTURLSessionDelegate.init()
// Add custom RUM resource attributes. It is recommended to add the project abbreviation prefix to tag names, e.g., `df_tag_name`.
delegate.provider = { request,response,data,error in
            var extraData:Dictionary<String, Any> = Dictionary()
            if let data = data,let requestBody = String(data: data, encoding: .utf8) {
                extraData["df_requestBody"] = requestBody
            }
            if let error = error {
                extraData["df_error"] = error.localizedDescription
            }
            return extraData
        }
// Support custom trace. Return TraceContext after confirming interception, return nil if not intercepted.   
delegate.traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }  
delegate.errorFilter = { error in
    return (error as? URLError)?.code == .cancelled
}        
let session =  URLSession.init(configuration: URLSessionConfiguration.default, delegate:delegate 
, delegateQueue: nil)

Method Two

Make the delegate object of the URLSession inherit from the FTURLSessionDelegate class.

If the delegate object implements the following methods, be sure to call the corresponding method of the parent class within the method.

  • -URLSession:dataTask:didReceiveData:
  • -URLSession:task:didCompleteWithError:
  • -URLSession:task:didFinishCollectingMetrics:
@interface InstrumentationInheritClass:FTURLSessionDelegate
@property (nonatomic, strong) NSURLSession *session;
@end
@implementation InstrumentationInheritClass
-(instancetype)init{
    self = [super init];
    if(self){
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
        // Add custom RUM resource attributes. It is recommended to add the project abbreviation prefix to tag names, e.g., `df_tag_name`.
        self.provider = ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
        NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
        return @{@"df_requestbody":body};
    };
        // Support custom trace. Return TraceContext after confirming interception, return nil if not intercepted.
       self.traceInterceptor = ^FTTraceContext * _Nullable(NSURLRequest *request) {
        FTTraceContext *context = [FTTraceContext new];
        context.traceHeader = @{@"trace_key":@"trace_value"};
        context.traceId = @"trace_id";
        context.spanId = @"span_id";
        return context;
       }; 
       // Custom whether to intercept SessionTask Error. return YES: intercept, RUM-Error will not add this `network_error`.   
       self.errorFilter = ^BOOL(NSError * _Nonnull error) {
          return error.code == NSURLErrorCancelled;
       }; 
    }
    return self;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
    // Must call the parent class method
    [super URLSession:session task:task didFinishCollectingMetrics:metrics];
    // Your own logic
    // ......
}
@end
class InheritHttpEngine:FTURLSessionDelegate {
    var session:URLSession?
    override init(){
        session = nil
        super.init()
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        session = URLSession.init(configuration: configuration, delegate:self, delegateQueue: nil)
        override init() {
        super.init()
        // Add custom RUM resource attributes. It is recommended to add the project abbreviation prefix to tag names, e.g., `df_tag_name`.
        provider = { request,response,data,error in
            var extraData:Dictionary<String, Any> = Dictionary()
            if let data = data,let requestBody = String(data: data, encoding: .utf8) {
                extraData["df_requestBody"] = requestBody
            }
            if let error = error {
                extraData["df_error"] = error.localizedDescription
            }
            return extraData
        }
        // Support custom trace. Return TraceContext after confirming interception, return nil if not intercepted.
        traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
        errorFilter = { error in
            return (error as? URLError)?.code == .cancelled
        }
    }
    }

    override func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        // Must call the parent class method
        super.urlSession(session, task: task, didFinishCollecting: metrics)
        // User's own logic
        // ......
    }
}

Method Three

Make the delegate object of the URLSession conform to the FTURLSessionDelegateProviding protocol.

  • Implement the get method for the ftURLSessionDelegate property in the protocol.
  • Forward the following URLSession delegate methods to ftURLSessionDelegate to enable SDK data collection.
    • -URLSession:dataTask:didReceiveData:
    • -URLSession:task:didCompleteWithError:
    • -URLSession:task:didFinishCollectingMetrics:
@interface UserURLSessionDelegateClass:NSObject<NSURLSessionDataDelegate,FTURLSessionDelegateProviding>
@end
@implementation UserURLSessionDelegateClass
@synthesize ftURLSessionDelegate = _ftURLSessionDelegate;

- (nonnull FTURLSessionDelegate *)ftURLSessionDelegate {
    if(!_ftURLSessionDelegate){
        _ftURLSessionDelegate = [[FTURLSessionDelegate alloc]init];
         // Add custom RUM resource attributes. It is recommended to add the project abbreviation prefix to tag names, e.g., `df_tag_name`.
        _ftURLSessionDelegate.provider =  ^NSDictionary * _Nullable(NSURLRequest *request, NSURLResponse *response, NSData *data, NSError *error) {
                NSString *body = [[NSString alloc] initWithData:request.HTMPBody encoding:NSUTF8StringEncoding];
                return @{@"df_requestbody":body};
            };
          // Support custom trace. Return TraceContext after confirming interception, return nil if not intercepted.
        _ftURLSessionDelegate.requestInterceptor = ^NSURLRequest * _Nonnull(NSURLRequest * _Nonnull request) {
            NSDictionary *traceHeader = [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:request.URL];
            NSMutableURLRequest *newRequest = [request mutableCopy];
            if(traceHeader){
                for (NSString *key in traceHeader.allKeys) {
                    [newRequest setValue:traceHeader[key] forHTTPHeaderField:key];
                }
            }
            return newRequest;
        }; 
        // Custom whether to intercept SessionTask Error. return YES: intercept, RUM-Error will not add this `network_error`.   
       _ftURLSessionDelegate.errorFilter = ^BOOL(NSError * _Nonnull error) {
           return error.code == NSURLErrorCancelled;
       };
    }
    return _ftURLSessionDelegate;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    [self.ftURLSessionDelegate URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    [self.ftURLSessionDelegate URLSession:session task:task didCompleteWithError:error];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
    [self.ftURLSessionDelegate URLSession:session task:task didFinishCollectingMetrics:metrics];
}
@end
class HttpEngine:NSObject,URLSessionDataDelegate,FTURLSessionDelegateProviding {
    var ftURLSessionDelegate: FTURLSessionDelegate = FTURLSessionDelegate()
    var session:URLSession?

    override init(){
        session = nil
        super.init()
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        session = URLSession.init(configuration: configuration, delegate:self, delegateQueue: nil)
        // Add custom RUM resource attributes. It is recommended to add the project abbreviation prefix to tag names, e.g., `df_tag_name`.
        ftURLSessionDelegate.provider = { request,response,data,error in
            var extraData:Dictionary<String, Any> = Dictionary()
            if let data = data,let requestBody = String(data: data, encoding: .utf8) {
                extraData["df_requestBody"] = requestBody
            }
            if let error = error {
                extraData["df_error"] = error.localizedDescription
            }
            return extraData
        }
        // Support custom trace. Return TraceContext after confirming interception, return nil if not intercepted.
        ftURLSessionDelegate.traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
        ftURLSessionDelegate.errorFilter = { error in
            return (error as? URLError)?.code == .cancelled
        }
    }
    // The following methods must be implemented.
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        ftURLSessionDelegate.urlSession(session, dataTask: dataTask, didReceive: data)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        ftURLSessionDelegate.urlSession(session, task: task, didFinishCollecting: metrics)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        ftURLSessionDelegate.urlSession(session, task: task, didCompleteWithError: error)
    }
}

User Binding and Unbinding

Use FTMobileAgent to bind user information and unbind the current user.

/// Bind user information. This method can be called after the user logs in successfully to bind user information.
///
/// - Parameters:
///   - Id:  User Id
///   - userName: User name (optional)
///   - userEmail: User email (optional)
///   - extra: User's extra information (optional)
- (void)bindUserWithUserID:(NSString *)Id userName:(nullable NSString *)userName userEmail:(nullable NSString *)userEmail extra:(nullable NSDictionary *)extra;

/// Unbind the current user. This method can be called after the user logs out to unbind user information.
- (void)unbindUser;
/// Bind user information. This method can be called after the user logs in successfully to bind user information.
///
/// - Parameters:
///   - Id:  User Id
///   - userName: User name (optional)
///   - userEmail: User email (optional)
///   - extra: User's extra information (optional)
open func bindUser(withUserID Id: String, userName: String?, userEmail: String?, extra: [AnyHashable : Any]?)

/// Unbind the current user. This method can be called after the user logs out to unbind user information.
open func unbindUser()

For addition rule precautions for extra, please refer to here.

Shut Down SDK

Use FTMobileAgent to shut down the SDK. Be sure to call it in the main thread, otherwise thread safety issues may occur. If you dynamically change the SDK configuration, you need to shut it down first to avoid generating erroneous data.

+ (void)shutDown;
open class func shutDown()

Clear SDK Cache Data

Use FTMobileAgent to clear unreported cache data.

+ (void)clearAllData;
open class func clearAllData()

Active Data Sync

Use FTMobileAgent to actively sync data.

FTMobileConfig.autoSync = NO, then you need to perform data synchronization manually.

- (void)flushSyncData;
func flushSyncData()

Active Dynamic Configuration Sync

Usage

Use FTMobileAgent to actively sync dynamic configuration. When automatic updates do not meet the requirements, adjust the update timing by actively calling the method.

FTMobileConfig.remoteConfiguration= YES, then calling the active dynamic configuration sync method takes effect.

/// Actively update remote configuration. The call frequency is affected by FTMobileConfig.remoteConfigMiniUpdateInterval.
+ (void)updateRemoteConfig;

/// Actively update remote configuration. This method ignores the FTMobileConfig.remoteConfigMiniUpdateInterval configuration.
/// - Parameters:
///   - miniUpdateInterval: Remote configuration time interval, unit seconds [0,)
///   - completion: Callback after the request is completed, supports custom adjustment of the configuration model in the callback.
+ (void)updateRemoteConfigWithMiniUpdateInterval:(NSInteger)miniUpdateInterval
                                     completion:(nullable FTRemoteConfigFetchCompletionBlock)completion;
/// Actively update remote configuration. The call frequency is affected by FTMobileConfig.remoteConfigMiniUpdateInterval.
 open class func updateRemoteConfig()

/// Actively update remote configuration. This method ignores the FTMobileConfig.remoteConfigMiniUpdateInterval configuration.
/// - Parameters:
///   - miniUpdateInterval: Remote configuration time interval, unit seconds [0,)
///   - completion: Callback after the request is completed, supports custom adjustment of the configuration model in the callback.
 open class func updateRemoteConfig(withMiniUpdateInterval miniUpdateInterval: Int, completion: FTRemoteConfigFetchCompletionBlock? = nil)

Remote Configuration Fetch Callback and Custom Configuration

SDK >= 1.5.19, supports custom modification of the final configuration via FTRemoteConfigFetchCompletionBlock return value.

/**
 *  @brief  Remote configuration fetch completion callback Block type.
 *  @details This Block is used to receive the fetch/parse result of the remote configuration, and returns the configuration model finally used by the SDK. The SDK adjusts corresponding functions based on the returned result.
 *
 *  @param  success   Boolean value, whether the fetch/parse was successful.
 *                    - YES: Operation successful, does not guarantee configuration data is non-empty.
 *                    - NO: Operation failed (function not enabled/not reached minimum time interval/network exception/data parsing failed, etc.).
 *  @param  error     Error information object, only returns valid error details when success=NO, always nil when success=YES.
 *  @param  model     Structured configuration model, only returns a valid instance when success=YES and configuration data is non-empty, otherwise returns nil.
 *  @param  content   Raw configuration dictionary, raw data without structured parsing, only returns a valid dictionary when success=YES and configuration data is non-empty, otherwise returns nil.
 *
 *  @return FTRemoteConfigModel optional instance, the configuration model finally used by the SDK.
 *          - Successful scenario (success=YES):
 *            1. Return non-nil instance: SDK uses this modified model to adjust functions.
 *            2. Return nil: SDK uses the originally parsed model (if it exists).
 *          - Failed scenario (success=NO):
 *            Must return nil, SDK ignores the callback result.
 */
typedef FTRemoteConfigModel*_Nullable(^FTRemoteConfigFetchCompletionBlock)(BOOL success,
                                              NSError * _Nullable error,
                                              FTRemoteConfigModel * _Nullable model,
                                              NSDictionary<NSString *, id> * _Nullable content);

Callback Priority: +updateRemoteConfigWithMiniUpdateInterval:completion: (exclusive callback for active method) > FTMobileConfig.remoteConfigFetchCompletionBlock (global unified callback).

Additional Notes:

  1. The exclusive completion of the active method only takes effect for the current active configuration fetch, with higher priority, suitable for "one-time personalized configuration processing" scenarios.
  2. The global remoteConfigFetchCompletionBlock takes effect for all remote configuration fetch behaviors (including automatic updates, active calls without exclusive completion, and when the exclusive completion returns nil), suitable for "global unified configuration processing" scenarios, set once and effective throughout.

Usage Example

The current example only shows the callback for the active sync method. The usage logic of the global remoteConfigFetchCompletionBlock is consistent with this example.

```objective-c [FTMobileAgent updateRemoteConfigWithMiniUpdateInterval:0 completion:^FTRemoteConfigModel * _Nullable(BOOL success, NSError * _Nullablererror, FTRemoteConfigModel * _Nullable model, NSDictionary * _Nullable content) { if (error) { NSLog(@"remoteConfigFetch error:%@",error.description); }

} // Process configuration data when the operation is successful. if (success) { // Get custom environment variable values from remote configuration. // Example: Need to adjust for specified users, specified user is uid = @"user_1". NSString *userId = content[@"custom_userid"]; if ([userId isEqualToString:@"user_1"]) { model.rumSampleRate = @(1); model.logSampleRate = @(1); model.traceSampleRate = @(1); } } // Return the modified model (returning model is equivalent to returning nil when not modified, both use the original model). return model; }]; ```

FTMobileAgent.updateRemoteConfig(withMiniUpdateInterval: 0) { (success: Bool, error: Error?, model: FTRemoteConfigModel?, content: [String: Any]?) -> FTRemoteConfigModel? in
                if let error = errofr {
                    print("remoteConfigFetch error:\(error.localizedDescription)")
                }

                // Process configuration data when the operation is successful.
                if success {
                    // Get custom environment variable values from remote configuration.
                    // Example: Need to adjust for specified users, specified user is uid = @"user_1".
                    let userId = content?["custom_userid"] as? String
                    if userId == "user_1" {
                        model?.rumSampleRate = 1
                        model?.logSampleRate = 1
                        model?.traceSampleRate = 1
                    }
                }

                // Return the modified model (returning model is equivalent to returning nil when not modified, both use the original model).
                return model
            }

Add Custom Tags

Use FTMobileAgent to dynamically add tags while the SDK is running. For addition rule precautions, please refer to here.

/// Add SDK global tag, effective for RUM, Log data.
/// - Parameter context: Custom data.
+ (void)appendGlobalContext:(NSDictionary <NSString*,id>*)context;

/// Add RUM custom tag, effective for RUM data.
/// - Parameter context: Custom data.
+ (void)appendRUMGlobalContext:(NSDictionary <NSString*,id>*)context;

/// Add Log global tag, effective for Log data.
/// - Parameter context: Custom data.
+ (void)appendLogGlobalContext:(NSDictionary <NSString*,id>*)context;
/// Add SDK global tag, effective for RUM, Log data.
/// - Parameter context: Custom data.
open class func appendGlobalContext(_ context: [String : Any])

/// Add RUM custom tag, effective for RUM data.
/// - Parameter context: Custom data.
open class func appendRUMGlobalContext(_ context: [String : Any])

/// Add Log global tag, effective for Log data.
/// - Parameter context: Custom data.
open class func appendLogGlobalContext(_ context: [String : Any])

Symbol File Upload

Xcode Add Run Script Script (Only Supports Datakit [Local Deployment])

  1. XCode add custom Run Script Phase: Build Phases -> + -> New Run Script Phase.

  2. Copy the script into the Xcode project's build phase run script. Parameters such as <app_id>, <datakit_address>, <env>, <dataway_token> need to be set in the script.

  3. Script: FTdSYMUploader.sh

#Parameters that need to be configured in the script.
#<app_id>
FT_APP_ID="YOUR_APP_ID"
#<datakit_address>
FT_DATAKIT_ADDRESS="YOUR_DATAKIT_ADDRESS"
#<env> Environment field. Attribute values: prod/gray/pre/common/local. Must be consistent with SDK settings.
FT_ENV="common"
#<dataway_token> Token for dataway in the datakit.conf configuration file.
FT_TOKEN="YOUR_DATAWAY_TOKEN"
# Whether to only package the dSYM file into a zip (optional, default 0 upload), 1=do not upload, only package dSYM zip, 0=upload. You can search for FT_DSYM_ZIP_FILE in the script output log to view the DSYM_SYMBOL.zip file path.
FT_DSYM_ZIP_ONLY=0

If you need to use multiple environments to upload symbol files for different environments, you can refer to the following method.

Multi-Environment Configuration Parameters

Example: Use .xcconfig configuration file to configure multiple environments.

1. Create an xcconfig configuration file and configure variables in the .xcconfig file.

For methods to create an xcconfig configuration file, please refer to: Adding a Build Configuration File to Your Project.

//If using cocoapods, you need to add the path of pods' .xcconfig to your .xcconfig file.
//Import the .xcconfig corresponding to the pod.
#include "Pods/Target Support Files/Pods-Demo/Pods-Demo.pre.xcconfig"

SDK_APP_ID = app_id_common
SDK_ENV = common
// URL // Need to add $()
SDK_DATAKIT_ADDRESS = http:/$()/xxxxxxxx:9529
SDK_DATAWAY_TOKEN = token

At this point, user-defined parameters have been automatically added. You can view them through Target —> Build Settings -> + -> Add User-Defined Setting.

2. Configure parameters in the script.

#Parameters that need to be configured in the script.
#<app_id>
FT_APP_ID=${SDK_APP_ID}
#<datakit_address>
FT_DATAKIT_ADDRESS=${SDK_DATAKIT_ADDRESS}
#<dev> Environment field. Attribute values: prod/gray/pre/common/local. Must be consistent with SDK settings.
FT_ENV=${SDK_ENV}
#<dataway_token> Token for dataway in the datakit.conf configuration file.
FT_TOKEN=${SDK_DATAWAY_TOKEN}

3. Configure SDK.

Do parameter mapping in the Info.plist file.

Get the parameters in Info.plist to configure the SDK.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let info = Bundle.main.infoDictionary!
        let appid:String = info["SDK_APP_ID"] as! String
        let env:String  = info["SDK_ENV"] as! String

        let config = FTMobileConfig.init(datakitUrl: UserDefaults.datakitURL)
        config.enableSDKDebugLog = true
        config.autoSync = false
        config.env = env
        .....
}

For detailed details, please refer to the multi-environment usage in SDK Demo.

Terminal Run Script

Script: FTdSYMUploader.sh

Symbol File Upload (Only Supports Datakit [Local Deployment])

sh FTdSYMUploader.sh <datakit_address> <app_id> <version> <env> <dataway_token> <dSYMBOL_src_dir>

Example:

sh FTdSYMUploader.sh http://10.0.0.1:9529 appid_mock 1.0.6 prod tkn_mock /Users/mock/Desktop/dSYMs

Only Compress Symbol Files

sh FTdSYMUploader.sh -dSYMFolderPath <dSYMBOL_src_dir> -z

Example:

sh FTdSYMUploader.sh -dSYMFolderPath /Users/mock/Desktop/dSYMs -z You can search for FT_DSYM_ZIP_FILE in the script output log to view the Zip file path.

Parameter Description:

  • <datakit_address>: DataKit service address, such as http://localhost:9529.
  • <app_id>: Corresponds to RUM's applicationId.
  • <env>: Corresponds to RUM's env.
  • <version>: Application version, the value of CFBundleShortVersionString.
  • <dataway_token>: Token for dataway in the datakit.conf configuration file.
  • <dSYMBOL_src_dir>: Directory path containing all .dSYM files.

Manual Upload

Sourcemap Upload

Widget Extension Data Collection

Widget Extension Data Collection Support

  • Logger custom logs.
  • Trace link tracing.
  • RUM data collection.

Since HTTP Resource data is bound to Views, users need to manually collect View data.

Widget Extension Collection Configuration

Use FTExtensionConfig to configure the automatic switch for Widget Extension data collection and the file sharing Group Identifier. Other configurations use the already set configurations from the main project SDK.

Field Type Required Description
groupIdentifier NSString Yes File sharing Group Identifier.
enableSDKDebugLog BOOL No (Default NO) Set whether to allow SDK to print Debug logs.
enableTrackAppCrash BOOL No (Default NO) Set whether to collect crash logs.
enableRUMAutoTraceResource BOOL No (Default NO) Set whether to track user network requests (only affects native http).
enableTracerAutoTrace BOOL No (Default NO) Set whether to enable automatic http link tracing.
memoryMaxCount NSInteger No (Default 1000 entries) Maximum number of data stored in Widget Extension.

Widget Extension SDK usage example:

let extensionConfig = FTExtensionConfig.init(groupIdentifier: "group.identifier")
extensionConfig.enableTrackAppCrash = true
extensionConfig.enableRUMAutoTraceResource = true
extensionConfig.enableTracerAutoTrace = true
extensionConfig.enableSDKDebugLog = true
FTExtensionManager.start(with: extensionConfig)
FTExternalDataManager.shared().startView(withName: "WidgetDemoEntryView")

At the same time, when setting FTMobileConfig in the main project, groupIdentifiers must be set.

// Main project.
 FTMobileConfig *config = [[FTMobileConfig alloc]initWithMetricsUrl:url];
 config.enableSDKDebugLog = YES;
 config.groupIdentifiers = @[@"group.com.ft.widget.demo"]; 
let config = FTMobileConfig.init(metricsUrl: url)
config.enableSDKDebugLog = true
config.groupIdentifiers = ["group.com.ft.widget.demo"]

Widget Extension Collected Data Upload

The Widget Extension SDK only implements data collection. The data upload logic is handed over to the main project's SDK to implement. The timing for synchronizing collected data to the main project is customized by the user.

Usage

// Call in the main project.
/// Track data cached in the App Extension groupIdentifier.
/// - Parameters:
///   - groupIdentifier: groupIdentifier.
///   - completion: Callback after track is completed.
- (void)trackEventFromExtensionWithGroupIdentifier:(NSString *)groupIdentifier completion:(nullable void (^)(NSString *groupIdentifier, NSArray *events)) completion;
/// Track data cached in the App Extension groupIdentifier.
/// - Parameters:
///   - groupIdentifier: groupIdentifier.
///   - completion: Callback after track is completed.
open func trackEventFromExtension(withGroupIdentifier groupIdentifier: String, completion: ((String, [Any]) -> Void)? = nil)

Code Example

// In the main project.
-(void)applicationDidBecomeActive:(UIApplication *)application{
    [[FTMobileAgent sharedInstance] trackEventFromExtensionWithGroupIdentifier:@"group.identifier" completion:nil];
}
func applicationDidBecomeActive(_ application: UIApplication) {
   FTMobileAgent.sharedInstance().trackEventFromExtension(withGroupIdentifier: "group.identifier" )     
}

WebView Data Monitoring

WebView data monitoring requires integrating the Web Monitoring SDK on the WebView access page.

Custom Tag Usage Example

Compilation Configuration Method

Multiple Configurations can be created, and precompilation instructions can be used to set values.

  1. Create multiple Configurations.

  1. Set preset properties to distinguish different Configurations.

  1. Use precompilation instructions.
//Target -> Build Settings -> GCC_PREPROCESSOR_DEFINITIONS to configure preset definitions.
#if PRE
#define Track_id       @"0000000001"
#define STATIC_TAG     @"preprod"
#elif  DEVELOP
#define Track_id       @"0000000002"
#define STATIC_TAG     @"common"
#else
#define Track_id       @"0000000003"
#define STATIC_TAG     @"prod"
#endif

FTRumConfig *rumConfig = [[FTRumConfig alloc]init]; 
rumConfig.globalContext = @{@"track_id":Track_id,@"static_tag":STATIC_TAG};
... //Other setting operations.
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];

You can also refer to the Multi-Environment Configuration Parameters method for configuration.

Runtime Read/Write File Method

Since the globalContext set after RUM is started will not take effect, users can save it locally and set it to take effect the next time the application starts.

  1. Save locally via file storage, such as NSUserDefaults. Configure using the SDK and add code to obtain tag data at the configuration location.
NSString *dynamicTag = [[NSUserDefaults standardUserDefaults] valueForKey:@"DYNAMIC_TAG"]?:@"NO_VALUE";

FTRumConfig *rumConfig = [[FTRumConfig alloc]init];
rumConfig.globalContext = @{@"dynamic_tag":dynamicTag};
... //Other setting operations.
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
  1. Add a method to change the file data anywhere.
 [[NSUserDefaults standardUserDefaults] setValue:@"dynamic_tags" forKey:@"DYNAMIC_TAG"];
  1. Finally, restart the application to take effect.

Add During SDK Runtime

After the SDK initialization is completed, use [FTMobileAgent appendGlobalContext:globalContext], [FTMobileAgent appendRUMGlobalContext:globalContext], [FTMobileAgent appendLogGlobalContext:globalContext] to dynamically add tags. After setting, it will take effect immediately. Subsequently, RUM or Log data reported later will automatically add tag data. This usage is suitable for scenarios where data acquisition is delayed, such as tag data that needs to be obtained through network requests.

//SDK initialization pseudo code, get.
[FTMobileAgent startWithConfigOptions:config];

-(void)getInfoFromNet:(Info *)info{
    NSDictionary *globalContext = @{@"delay_key", info.value}
    [FTMobileAgent appendGlobalContext:globalContext];
}

tvOS Data Collection

api >= tvOS 12.0

The initialization and use of the SDK are consistent with the iOS side.

Note that tvOS does not support:

  • WebView data detection.

  • Device battery monitoring in FTRumConfig.errorMonitorType.

Common Questions

About Crash Log Analysis

In Debug and Release modes during development, the thread backtrace captured during a Crash is symbolized. However, the release package does not carry the symbol table. The key backtrace of the exception thread will display the name of the image and will not be converted into valid code symbols. The related information obtained in the crash log are all 16-bit memory addresses and cannot locate the crashing code. Therefore, it is necessary to parse the 16-bit memory addresses into the corresponding classes and methods.

How to Find the dSYM File After Compilation or Packaging

  • In Xcode, the dSYM file is usually generated together with the compiled .app file and located in the same directory.
  • If the project has been archived, you can select Organizer in the Window menu of Xcode, then select the corresponding archive file. Right-click the archive file and select Show in Finder”. In Finder, find the corresponding.xcarchivefile. Right-click the.xcarchivefile, selectShow Package Contents, then enter thedSYMs` folder to find the corresponding dSYM file.

XCode Does Not Generate dSYM File After Compilation?

XCode Release compilation generates dSYM files by default, while Debug compilation does not generate them by default. The corresponding Xcode configuration is as follows:

Build Settings -> Code Generation -> Generate Debug Symbols -> Yes

Build Settings -> Build Option -> Debug Information Format -> DWARF with dSYM File

How to Upload the Symbol Table When BitCode is Enabled?

When you upload your bitcode App to the App Store, check the declaration of symbol file (dSYM file) generation in the submission dialog:

  • Before configuring the symbol table file, you need to download the dSYM file corresponding to this version from the App Store to the local, and then use the script to process and upload the symbol table file according to the input parameters.
  • There is no need to integrate the script into the Target of the Xcode project, nor use the locally generated dSYM file to generate the symbol table file, because the symbol table information in the locally compiled dSYM file is hidden. If you upload using the locally compiled dSYM file, the restored result will be a symbol similar to "__hiden#XXX".

How to Retrieve the dSYM File Corresponding to the App Already Published to the App Store?

Distribution options for the app uploaded to App Store Connect dSym file
Don’t include bitcode
Upload symbols
Retrieve via Xcode.
Include bitcode
Upload symbols
Retrieve via iTunes Connect.
Retrieve via Xcode, requires using .bcsymbolmap for de-obfuscation processing.
Include bitcode
Don’t upload symbols
Retrieve via Xcode, requires using .bcsymbolmap for de-obfuscation processing.
Don’t include bitcode
Don’t upload symbols
Retrieve via Xcode.
Retrieve via Xcode
  1. Xcode -> Window -> Organizer.

  2. Select the Archives tab.

  3. Find the published archive package, right-click the corresponding archive package, and select Show in Finder.

  4. Right-click the located archive file and select Show Package Contents.

  5. Select the dSYMs directory. The dSYM file is located inside this directory.

Retrieve via iTunes Connect
  1. Log in to App Store Connect;
  2. Go to "My Apps (My Apps)".
  3. In "App Store" or "TestFlight", select a certain version", click "Build Version Metadata (Build Metadata)". On this page, click the button "Download dSYM (Download dSYM)" to download the dSYM file.
.bcsymbolmap De-obfuscation Processing

When finding the dSYM file via Xcode, you can see the BCSymbolMaps directory.

Open the terminal and use the following command for de-obfuscation processing.

xcrun dsymutil -symbol-map <BCSymbolMaps_path> <.dSYM_path>

Add Global Variables to Avoid Conflicting Fields

To avoid conflicts between custom fields and SDK data, it is recommended to add the project abbreviation prefix to tag names, such as df_tag_name. The key values used in the project can be queried from the source code. When the same variable appears in the SDK global variables and RUM, Log, RUM and Log will override the global variables in the SDK.