Skip to content

iOS/tvOS Application Integration


By collecting metrics data from various iOS applications, the performance of each iOS application can be analyzed in a visualized manner.

Prerequisites

Note

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

Application Integration

  1. Go to User Analysis > Create > iOS;
  2. Input the application name;
  3. Input 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 Address: https://github.com/GuanceCloud/datakit-ios

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

  1. Configure the Podfile.

    • Use Dynamic Library

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

    • Use 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 repository locally for use

    Podfile:

    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: Change it to the specified version; it is recommended to align 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.

    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 xcframeworks are used similarly to ordinary Frameworks. Add the compiled libraries to your project.

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

    FTMobileExtension: Add to the Widget Extension Target.

  3. In TARGETS -> Build Setting -> Other Linker Flags, add -ObjC.

  4. When using Carthage integration, the SDK version supported is:

    FTMobileAgent: >=1.3.4-beta.2

    FTMobileExtension: >=1.4.0-beta.1

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

  2. Enter https://github.com/GuanceCloud/datakit-ios.git in the search box that pops up.

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

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

    Add To Project: Select the supported project.

    After configuring, 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 where the SDK needs to be added, and click the Add Package button. At this point, the SDK has been successfully added.

    FTMobileSDK: Add to the main project Target

    FTMobileExtension: Add to the Widget Extension Target

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

    // Main project
    dependencies: [
        .package(name: "FTMobileSDK", url: "https://github.com/GuanceCloud/datakit-ios.git",
        .upToNextMajor(from: "[latest_version]"))]
    
  5. Supports Swift Package Manager starting from 1.4.0-beta.1.

Adding Header Files

//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];
     // Using 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)
       // Using public DataWay deployment
     let config = FTMobileConfig(datawayUrl: datawayUrl, clientToken: clientToken)
     //config.enableSDKDebugLog = true              //debug mode
     config.compressIntakeRequests = true           //Compress reported data
     FTMobileAgent.start(withConfigOptions: config)
     //...
     return true
}
Property 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 either datakitUrl or datawayUrl
datawayUrl NSString Yes Public Dataway reporting URL address, obtained from [User Analysis] application, example: https://open.dataway.url, the device installing the SDK must be able to access this address. Note: Choose either datakitUrl or datawayUrl
clientToken NSString Yes Authentication token, must 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 via the provided FTEnv enumeration through the -setEnvWithType: method
service NSString No Set the name of the associated business or service. Affects the service field data in Log and RUM. Default: df_rum_ios
globalContext NSDictionary No Add custom tags. Refer to here for addition rules
groupIdentifiers NSArray No Array of AppGroups Identifier corresponding to the Widget Extensions that need to be collected. If Widget Extensions data collection is enabled, then this property must be set App Groups, and configure the Identifier into this property
autoSync BOOL No Whether to automatically synchronize 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 synchronization request. Range [5,). Note: The larger the number of requests, the more computational resources data synchronization consumes, default is 10
syncSleepTime int No Set the intermittent time for synchronization. Range [0,5000], default not set
enableDataIntegerCompatible BOOL No When coexisting with web data, it is recommended to enable this. This configuration is used to handle web data type storage compatibility issues.
compressIntakeRequests BOOL No Deflate compression for uploaded synchronized data. Supported by SDK version 1.5.6 and above, default disabled
enableLimitWithDbSize BOOL No Enable DB cache size limit function.
Note: Enabling this makes FTLoggerConfig.logCacheLimitCount and FTRUMConfig.rumCacheLimitCount ineffective. Supported by SDK version 1.5.8 and above
dbCacheLimit long No DB cache size limit. Range [30MB,), default 100MB, unit byte, supported by SDK version 1.5.8 and above
dbDiscardType FTDBCacheDiscard No Set data discard rule in the database. Default FTDBDiscard
FTDBDiscard discards appended data when the number of data exceeds the maximum value. FTDBDiscardOldest discards old data when the number of data exceeds the maximum value. Supported by SDK version 1.5.8 and above
dataModifier FTDataModifier No Modify individual fields. Supported by SDK version 1.5.16 and above, refer to here for usage examples
lineDataModifier FTLineDataModifier No Modify single-line data. Supported by SDK version 1.5.16 and above, refer to here for usage examples
remoteConfiguration BOOL No Whether to enable remote configuration functionality for data collection, default not enabled. When enabled, SDK initialization or app hot restart triggers data updates. Supported by SDK version 1.5.17 and above. Datakit version requirement >=1.60 or use public dataway
remoteConfigMiniUpdateInterval int No Set the minimum update interval for remote dynamic configuration, unit seconds, default 12 hours. Supported by SDK version 1.5.17 and above

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)
Property Type Required Meaning
appid NSString Yes Unique identifier for the User Analysis application ID. Corresponds to setting the RUM appid, which enables the RUM collection feature, 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 applies to all View, Action, LongTask, Error data within the same session_id
sessionOnErrorSampleRate int No Set error collection rate, when the session is not sampled by samplerate, if an error occurs during the session, data within one minute before the error can be collected. Value range [0,100], 0 means no collection, 100 means full collection, default value is 0. Scope applies to all View, Action, LongTask, Error data within the same session_id. Supported by SDK version 1.5.16 and above
enableTrackAppCrash BOOL No Set whether to collect crash logs. Default NO
enableTrackAppANR BOOL No Collect ANR freeze unresponsive events. Default NO
enableTrackAppFreeze BOOL No Collect UI freeze events. Default NO
Can be enabled via the -setEnableTrackAppFreeze:freezeDurationMs: method along with setting the freeze threshold
freezeDurationMs long No Set the UI freeze threshold, value range [100,), unit milliseconds, default 250ms. Supported by SDK version 1.5.7 and above
enableTraceUserView BOOL No Set whether to trace user View operations. Default NO
enableTraceUserAction BOOL No Set whether to trace user Action operations. Default NO
enableTraceUserResource BOOL No Set whether to trace user network requests. Default NO, only applicable to native http
Note: Network requests initiated via [NSURLSession sharedSession] cannot collect performance data;
SDK version 1.5.9 and above support collecting network requests initiated via Swift's URLSession async/await APIs.
resourceUrlHandler FTResourceUrlHandler No Customize resource collection rules. Default does not filter. Returns: NO indicates to collect, YES indicates not to collect.
errorMonitorType FTErrorMonitorType No Supplementary type for error event monitoring. Adds monitored information to the collected crash data. FTErrorMonitorBattery for battery level, FTErrorMonitorMemory for memory usage, FTErrorMonitorCpu for CPU occupancy rate.
deviceMetricsMonitorType FTDeviceMetricsMonitorType No Performance monitoring type for views. Adds corresponding monitoring items to the collected View data. FTDeviceMetricsMonitorMemory monitors current application memory usage, FTDeviceMetricsMonitorCpu monitors CPU jump count, FTDeviceMetricsMonitorFps monitors screen frame rate.
monitorFrequency FTMonitorFrequency No Performance monitoring sampling cycle for views. Configure monitorFrequency to set the sampling cycle for View monitoring item information. FTMonitorFrequencyDefault500ms (default), FTMonitorFrequencyFrequent100ms, FTMonitorFrequencyRare1000ms.
enableResourceHostIP BOOL No Whether to collect the IP address of the requested target domain name. Supported on >= iOS 13.0 >= tvOS 13.0
globalContext NSDictionary No Add custom tags for distinguishing user monitoring data sources. If tracking functionality is required, the key should be track_id, and value can be any numerical value. Refer to here for addition rule notes
rumCacheLimitCount int No Maximum RUM cache size. Default 100_000, supported by SDK version 1.5.8 and above
rumDiscardType FTRUMCacheDiscard No Set RUM discard rule. Default FTRUMCacheDiscard
FTRUMCacheDiscard discards appended data when the number of RUM data exceeds the maximum value. FTRUMDiscardOldest discards old data when the number of RUM data exceeds the maximum value. Supported by SDK version 1.5.8 and above
resourcePropertyProvider FTResourcePropertyProvider No Add custom attributes to RUM Resource via block callback. Supported by SDK version 1.5.10 and above. Priority lower than URLSession custom collection
enableTraceWebView BOOL No Set to enable WebView data collection, default YES. Supported by SDK version 1.5.17 and above
allowWebViewHost NSArray No Set specific hosts or domains for WebView data collection, nil for full collection. Supported by SDK version 1.5.17 and above
sessionTaskErrorFilter FTSessionTaskErrorFilter No Set whether to intercept SessionTask Error, return YES to confirm interception, NO otherwise. Supported by SDK version 1.5.17 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)
Property 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 log. Default NO
printCustomLogToConsole BOOL No Set whether to output custom logs to the console. Default NO, refer to output format for custom logs
logLevelFilter NSArray No Set log level filtering, default not set. Example:
1. Collect logs at Info and Error levels, set as @[@(FTStatusInfo),@(FTStatusError)] or @[@0,@1]
2. Collect logs including custom levels, such as collecting "customLevel" and Error, set as @[@"customLevel",@(FTStatusError)]
SDK version 1.5.16 and above support filtering custom levels
enableLinkRumData BOOL No Whether to associate with RUM data. Default NO
globalContext NSDictionary No Add custom tags to logs. Refer to here for addition rules
logCacheLimitCount int No Local cache maximum log entry limit [1000,), larger logs indicate greater disk cache pressure, default 5000
discardType FTLogCacheDiscard No Set log discard rule when log reaches the limit. Default FTDiscard
FTDiscard discards appended data when the number of log data exceeds the maximum value (5000). FTDiscardOldest discards old data when the number of log data exceeds the maximum value.

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)
Property 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 selecting corresponding link type while integrating OpenTelemetry, please refer to supported types and related agent configurations
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 determining whether to perform custom link tracing via URLRequest, return TraceContext if confirmed interception, return nil otherwise. Supported by SDK version 1.5.10 and above. Priority lower than URLSession custom collection

RUM User Data Tracing

Configure FTRUMConfig with enableTraceUserAction, enableTraceUserView, enableTraceUserResource, enableTrackAppFreeze, enableTrackAppCrash, and enableTrackAppANR to achieve automatic collection tracking of Action, View, Resource, LongTask, and Error data. If you want custom collection, you can report data through FTExternalDataManager, as shown below:

View

Usage Method

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

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

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

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

/// Leave the page
/// - Parameter property: Event custom properties (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 Method

/// Start RUM Action.
///
/// RUM binds possible Resource, Error, LongTask events triggered by this Action. Avoid multiple additions within 0.1 s, only one Action can be associated with the same View at the same time, new Actions added while the previous Action is not finished will be discarded.
/// Adding Actions using `addAction:actionType:property` method does not affect each other.
///
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom properties (optional)
- (void)startAction:(NSString *)actionName actionType:(NSString *)actionType property:(nullable NSDictionary *)property;

/// Add Action event. No duration, no discard logic
///
/// Adding Actions using `startAction:actionType:property:` method does not affect each other.
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom properties (optional)
- (void)addAction:(NSString *)actionName actionType:(NSString *)actionType property:(nullable NSDictionary *)property;
/// Start RUM Action.
///
/// RUM binds possible Resource, Error, LongTask events triggered by this Action. Avoid multiple additions within 0.1 s, only one Action can be associated with the same View at the same time, new Actions added while the previous Action is not finished will be discarded.
/// Adding Actions using `addAction:actionType:property` method does not affect each other.
///
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom properties (optional)
open func startAction(_ actionName: String, actionType: String, property: [AnyHashable : Any]?)

/// Add Action event. No duration, no discard logic
///
/// Adding Actions using `startAction:actionType:property:` method does not affect each other.
/// - Parameters:
///   - actionName: Event name
///   - actionType: Event type
///   - property: Event custom properties (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 Method

/// Add Error event
/// - Parameters:
///   - type: error type
///   - message: Error message
///   - stack: Stack information
///   - property: Event custom properties (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 properties (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 properties (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 properties (optional)
open func addError(withType type: String, state: FTAppState, message: String, stack: String, property: [AnyHashable : Any]?)

Code Example

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

LongTask

Usage Method

/// Add freeze event
/// - Parameters:
///   - stack: Freeze stack
///   - duration: Freeze duration (nanoseconds)
///   - property: Event custom properties (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 properties (optional)
func addLongTask(withStack: String, duration: NSNumber, property: [AnyHashable : Any]?)

Code Example

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

Resource

Usage Method

```objectivec /// HTTP request starts /// - Parameters: /// - key: Request identifier /// - property: Event custom properties (optional) - (void)startResourceWithKey:(NSString *)key property:(nullable NSDictionary *)property;

/// HTTP adds request data /// /// - Parameters: /// - key: Request identifier /// - metrics: Request-related performance attributes /// - content: Request-related data - (void)addResourceWithKey:(NSString *)key metrics:(nullable```objectivec

FTResourceMetricsModel *)metrics content:(FTResourceContentModel *)content;

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

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

/// HTTP adds 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 the request starts
[[FTExternalDataManager sharedManager] startResourceWithKey:key];

// Step 2: When the request completes
[[FTExternalDataManager sharedManager] stopResourceWithKey:key];

// Step 3: Concatenate 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 stage can be obtained
// FTResourceMetricsModel
// For ios native, obtain NSURLSessionTaskMetrics data directly using FTResourceMetricsModel's initialization method
FTResourceMetricsModel *metricsModel = [[FTResourceMetricsModel alloc]initWithTaskMetrics:metrics];

// For 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 the request starts
FTExternalDataManager.shared().startResource(withKey: key)

// Step 2: When the request completes
FTExternalDataManager.shared().stopResource(withKey: resource.key)

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

// ② If time data for each stage can be obtained 
// FTResourceMetricsModel
// For ios native, obtain NSURLSessionTaskMetrics data directly using FTResourceMetricsModel's initialization method
var metricsModel:FTResourceMetricsModel?
if let metrics = resource.metrics {
   metricsModel = FTResourceMetricsModel(taskMetrics:metrics)
}
// For 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

When initializing the SDK with Log Configuration, configure enableCustomLog to allow adding custom logs.

Currently, log content is limited to 30 KB, and any characters exceeding this limit will be truncated.

Usage Method

//
//  FTLogger.h
//  FTMobileSDK

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

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

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

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

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

/// Add custom log
/// - Parameters:
///   - content: Log content
///   - status: Log status level
///   - property: Custom properties (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 properties (optional)
optional func info(_ content: String, property: [AnyHashable : Any]?)

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

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

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

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

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

Log Levels

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

Code Example

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

Custom Logs Output to Console

Set printCustomLogToConsole = YES to enable outputting custom logs to the console. You will see logs of 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 used to distinguish SDK output custom logs;

[INFO]: Custom log level;

content: Custom log content;

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

You can enable automatic mode through FTTraceConfig, or support user-defined addition of trace-related data. The relevant API for custom addition is as follows:

NSString *key = [[NSUUID UUID]UUIDString];
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
// Manual operation required: Get 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: Get 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()
}

Intercept URLSession Delegate to Customize Network Collection

The SDK provides a class FTURLSessionDelegate, which can be used to customize RUM Resource collection and trace tracking for network requests initiated by a certain URLSession.

  • FTURLSessionDelegate supports setting the traceInterceptor block to intercept URLResquest for custom trace tracking (supported by SDK version 1.5.9 and above), with higher priority than FTTraceConfig.traceInterceptor.
  • FTURLSessionDelegate supports setting the provider block to customize additional properties that need to be collected for RUM Resource, with higher priority than FTRumConfig.resourcePropertyProvider.
  • FTURLSessionDelegate supports setting the errorFilter block to customize whether to intercept SessionTask Errors. (Supported by SDK version 1.5.17 and above)
  • return YES: Intercept, do not add this network_error to RUM-Error
  • return NO: Do not intercept, add this network_error to RUM-Error
  • When used together with FTRumConfig.enableTraceUserResource and FTTraceConfig.enableAutoTrace, priority: Custom > Automatic Collection.

Below are three methods provided to meet different user scenarios.

Method One

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

id<NSURLSessionDelegate> delegate = [[FTURLSessionDelegate alloc]init];
// Add custom RUM resource properties, it is recommended to add 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 upon confirmation of interception, return nil otherwise
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;
    };  
// Customize whether to intercept SessionTask Errors. Return YES: Intercept, do not add this `network_error` to RUM-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 properties, it is recommended to add 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 upon confirmation of interception, return nil otherwise   
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 inherit from FTURLSessionDelegate class.

If the delegate object implements the following methods, ensure that the corresponding methods in the parent class are called within these methods.

  • -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 properties, it is recommended to add 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 upon confirmation of interception, return nil otherwise
       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;
       }; 
       // Customize whether to intercept SessionTask Errors. Return YES: Intercept, do not add this `network_error` to RUM-Error   
       self.errorFilter = ^BOOL(NSError * _Nonnull error) {
          return error.code == NSURLErrorCancelled;
       }; 
    }
    return self;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
    // Make sure to 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 properties, it is recommended to add 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 upon confirmation of interception, return nil otherwise
        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) {
        // Make sure to call the parent class method
        super.urlSession(session, task: task, didFinishCollecting: metrics)
        // User's own logic
        // ......
    }
}

Method Three

Make the delegate object conform to the FTURLSessionDelegateProviding protocol.

  • Implement the get method for the ftURLSessionDelegate property in the protocol
  • Forward the following URLSession delegate methods to ftURLSessionDelegate so that the SDK can collect data.
    • -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 properties, it is recommended to add 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.HTTPBody encoding:NSUTF8StringEncoding];
                return @{@"df_requestbody":body};
            };
          // Support custom trace, return TraceContext upon confirmation of interception, return nil otherwise
        _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;
        }; 
        // Customize whether to intercept SessionTask Errors. Return YES: Intercept, do not add this `network_error` to RUM-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 properties, it is recommended to add 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 upon confirmation of interception, return nil otherwise
        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
        }
    }
    // Below 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)
    }
}

Binding and Unbinding Users

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

/// Bind user information, call this method after successful user login to bind user information
///
/// - Parameters:
///   - Id: User ID
///   - userName: User name (optional)
///   - userEmail: User email (optional)
///   - extra: Additional user information (optional)
- (void)bindUserWithUserID:(NSString *)Id userName:(nullable NSString *)userName userEmail:(nullable NSString *)userEmail extra:(nullable NSDictionary *)extra;

/// Unbind the current user, call this method after the user logs out to unbind user information
- (void)unbindUser;
/// Bind user information, call this method after successful user login to bind user information
///
/// - Parameters:
///   - Id: User ID
///   - userName: User name (optional)
///   - userEmail: User email (optional)
///   - extra: Additional user information (optional)
open func bindUser(withUserID Id: String, userName: String?, userEmail: String?, extra: [AnyHashable : Any]?)

/// Unbind the current user, call this method after the user logs out to unbind user information
open func unbindUser()

extra addition rules notes refer to here.

Closing the SDK

Use FTMobileAgent to close the SDK. If dynamically changing the SDK configuration, you need to close it first to avoid incorrect data generation.

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

Clearing SDK Cache Data

Use FTMobileAgent to clear uncached data that hasn't been reported yet.

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

Synchronizing Data Actively

Use FTMobileAgent to actively synchronize data.

Data synchronization needs to be done manually when FTMobileConfig.autoSync = NO.

- (void)flushSyncData;
func flushSyncData()

Actively Synchronize Dynamic Configuration

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

The active synchronization of dynamic configuration only takes effect when FTMobileConfig.remoteConfiguration= YES.

/// Actively update remote configuration, the frequency of calls 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,)
///   - callback: Returns the update result
+ (void)updateRemoteConfigWithMiniUpdateInterval:(int)miniUpdateInterval callback:(void (^)(BOOL success, NSDictionary<NSString *, id> * _Nullable config))callback;
/// Actively update remote configuration, the frequency of calls 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,)
///   - callback: Returns the update result
 open class func updateRemoteConfig(withMiniUpdateInterval miniUpdateInterval: Int32, callback: @escaping (Bool, [String : Any]?) -> Void)

Adding Custom Tags

Use FTMobileAgent to dynamically add tags during SDK runtime. Refer to here for addition rule notes.

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

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

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

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

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

Symbol File Upload

Adding Run Script in Xcode (only supported for datakit [local deployment])

  1. Add a custom Run Script Phase in Xcode: Build Phases -> + -> New Run Script Phase

  2. Copy the script into the run script section of the Xcode project, and set parameters such as <app_id>, <datakit_address>, <env>, <dataway_token> 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. Property values: prod/gray/pre/common/local. Must be consistent with SDK settings
FT_ENV="common"
#<dataway_token> Token in the datakit.conf configuration file for dataway
FT_TOKEN="YOUR_DATAWAY_TOKEN"
# Whether to only zip dSYM files (optional, default 0 upload), 1=do not upload, only zip dSYM, 0=upload, 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 upload symbol files for multiple environments, you can refer to the following method.

Multi-environment Configuration Parameters

Example: Use .xcconfig configuration file for multi-environment

1. Create xcconfig configuration file, configure variables in the .xcconfig file

Create xcconfig configuration file method reference: Add build configuration file to your project

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

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

At this point, the user-defined parameters have been automatically added and can be viewed through Target —> Build Settings -> + -> Add User-Defined Setting.

2. Configure script parameters

# 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. Property values: prod/gray/pre/common/local. Must be consistent with SDK settings
FT_ENV=${SDK_ENV}
#<dataway_token> Token in the datakit.conf configuration file for dataway
FT_TOKEN=${SDK_DATAWAY_TOKEN}

3. Configure SDK

Map parameters in the Info.plist file

Get parameters from Info.plist and 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
        .....
}

Detailed information can be found in the SDK Demo for multi-environment usage.

Terminal Running Script

Script: FTdSYMUploader.sh

Symbol File Upload (only supported for 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

Compress Symbol Files Only

sh FTdSYMUploader.sh -dSYMFolderPath <dSYMBOL_src_dir> -z

Example:

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

Parameter Description:

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

Manual Upload

Source Map 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 and file sharing Group Identifier for Widget Extension data collection, other configurations use the already set configurations in the main project's 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 applicable to 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 saved in the 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, you must set groupIdentifiers.

// 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"]

Uploading Widget Extension Data

The Widget Extension SDK only implements data collection, and the data upload logic is handled by the main project's SDK. The timing for synchronizing collected data to the main project is defined by the user.

Usage Method

// Call in the main project
/// Track App Extension groupIdentifier cached data
/// - Parameters:
///   - groupIdentifier: groupIdentifier
///   - completion: Callback after completing track
- (void)trackEventFromExtensionWithGroupIdentifier:(NSString *)groupIdentifier completion:(nullable void (^)(NSString *groupIdentifier, NSArray *events))completion;
/// Track App Extension groupIdentifier cached data
/// - Parameters:
///   - groupIdentifier: groupIdentifier
///   - completion: Callback after completing track
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

To monitor WebView data, integrate the Web Monitoring SDK on the accessed webpage.

Data Masking

If you want to fully mask fields, it is recommended to use FTMobileConfig.dataModifier for better performance. If detailed rule replacement is required, it is recommended to use FTMobileConfig.lineDataModifier.

Do not use complex or time-consuming methods in the callback function, as this can greatly affect the SDK data writing performance.

FTMobileConfig *config = [[FTMobileConfig alloc]initWithDatakitUrl:DatakitUrl];
config.dataModifier = ^id _Nullable(NSString * _Nonnull key, id  _Nonnull value) {
        if ([key isEqualToString: @"device_uuid"]) {
            return @"xxx";
        }
        return nil;
    };
config.lineDataModifier = ^NSDictionary<NSString *,id> * _Nullable(NSString * _Nonnull measurement, NSDictionary<NSString *,id> * _Nonnull data) {
        if ([measurement isEqualToString:@"view"]) {
            return @{@"view_url":@"xxx"};
        }
        return nil;
    }; 
let config = FTMobileConfig(datakitUrl: DatakitUrl)
config.dataModifier = { (key: String, value: Any) -> Any? in
    if key == "device_uuid" {
        return "xxx"
    }
    return nil
}
config.lineDataModifier = { (measurement: String, data: [String: Any]) -> [String: Any]? in
    if measurement == "view" {
        return ["view_url": "xxx"]
    }
    return nil
}

Custom Tag Usage Example

Compile-time Configuration Method

You can create multiple Configurations and use preprocessor directives to set values.

  1. Create multiple Configurations

  1. Set predefined properties to distinguish different Configurations

  1. Use preprocessor directives
//Target -> Build Settings -> GCC_PREPROCESSOR_DEFINITIONS Configure predefined 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 File Read/Write Method

Since RUM does not take effect when globalContext is set after startup, users can save locally and set during the next app launch.

  1. Save files locally, for example using NSUserDefaults, configure the SDK with code to obtain tag data.
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 file data at any point.
 [[NSUserDefaults standardUserDefaults] setValue:@"dynamic_tags" forKey:@"DYNAMIC_TAG"];
  1. Finally, restart the application for changes to take effect.

Adding Tags Dynamically During SDK Runtime

After initializing the SDK, use [FTMobileAgent appendGlobalContext:globalContext], [FTMobileAgent appendRUMGlobalContext:globalContext], [FTMobileAgent appendLogGlobalContext:globalContext] to dynamically add tags. Once set, they will take effect immediately. Subsequently, all reported RUM or Log data will automatically include tag data. This usage method suits scenarios where tag data needs to be obtained through network requests.

// Pseudo-code for SDK initialization, get info
[FTMobileAgent startWithConfigOptions:config];

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

tvOS Data Collection

api >= tvOS 12.0

SDK initialization and usage are consistent with iOS.

Note that tvOS does not support:

  • WebView data detection

  • FTRumConfig.errorMonitorType for device battery monitoring

Common Issues

About Crash Log Analysis

In Debug and Release modes during development, the captured thread backtraces are symbolized. When publishing packages without symbol tables, the key backtraces of exceptions will show the image name and will not be converted into valid code symbols. The obtained crash log will contain 16-bit memory addresses and cannot locate the crash code, so it is necessary to parse the 16-bit memory addresses into corresponding classes and methods.

How to find dSYM files after compilation or packaging

  • In Xcode, the dSYM file is usually generated along with the compiled .app file and located in the same directory.
  • If the project has been archived, you can find it in Xcode’s Window menu by selecting Organizer, then choosing the corresponding archive. Right-click on the archive file and select Show in Finder. In Finder, find the corresponding .xcarchive file. Right-click on the .xcarchive file and choose Show Package Contents, then enter the dSYMs folder to find the corresponding dSYM file.

Why doesn't XCode generate dSYM files after compilation?

XCode Release compilation defaults to generating dSYM files, while Debug compilation defaults to not generating them. Corresponding Xcode configurations are as follows:

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

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

How to upload symbol tables when bitCode is enabled?

When uploading your bitcode App to the App Store, check the box in the submission dialog to declare the generation of symbol files (dSYM files):

  • Before configuring the symbol table file, download the corresponding dSYM file for the version from the App Store, then process and upload the symbol table file based on input parameters.
  • Do not 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 information in the locally compiled dSYM file is hidden. If the locally compiled dSYM file is uploaded, the result will be similar to “__hiden#XXX” symbols.

How to retrieve the dSYM file corresponding to an App already published to the App Store?

Application uploaded to App Store Connect Distribution Options dSym Files
Don’t include bitcode
Upload symbols
Retrieve via Xcode
Include bitcode
Upload symbols
Retrieve via iTunes Connect
Retrieve via Xcode, need to handle obfuscation using .bcsymbolmap.
Include bitcode
Don’t upload symbols
Retrieve via Xcode, need to handle obfuscation using .bcsymbolmap.
Don’t include bitcode
Don’t upload symbols
Retrieve via Xcode
Retrieving via Xcode
  1. Xcode -> Window -> Organizer

  2. Select the Archives tab

  3. Find the published archive, right-click on the corresponding archive and choose Show in Finder

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

  5. Choose the dSYMs directory, which contains the downloaded dSYM files

Retrieving via iTunes Connect
  1. Login to App Store Connect;
  2. Enter "My Apps";
  3. In "App Store" or "TestFlight", select a specific version, click "Build Metadata". On this page, click the button "Download dSYM" to download the dSYM file.
Handling Obfuscation with .bcsymbolmap

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

Open the terminal and use the following command to handle deobfuscation:

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

Adding Global Variables to Avoid Conflicting Fields

To avoid conflicts between custom fields and SDK data, it is recommended to add a project abbreviation prefix to tag names, such as df_tag_name. You can query source code for the keys used in the project. When global variables in the SDK appear identical to RUM and Log variables, RUM and Log will override the global variables in the SDK.

Feedback

Is this page helpful? ×