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 way.

Prerequisites

Note

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

Application Integration

  1. Enter User Analysis > Create > iOS;
  2. Input the application name;
  3. Input the application ID;
  4. Select the application integration method:

    • Public Network 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 where the FTMobileSDK.podspec file is located.

    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's recommended to align with SDK_VERSION in FTMobileSDK/FTMobileAgent/Core/FTMobileAgentVersion.h.

    s.source: tag => s.version

  2. Run 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 usage method of the generated xcframework is the same as 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 supported SDK versions are:

    FTMobileAgent: >=1.3.4-beta.2

    FTMobileExtension: >=1.4.0-beta.1

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

  2. In the search bar that pops up, input https://github.com/GuanceCloud/datakit-ios.git.

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

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

    Add To Project: Choose the supported project.

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

  4. In the popup Choose Package Products for datakit-ios, choose the Targets where you want to add the SDK, click the Add Package button. At this point, the SDK has been added successfully.

    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. Support Swift Package Manager from 1.4.0-beta.1 and above.

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 The URL address for reporting local environment deployment (Datakit). Example: http://10.0.0.1:9529, default port 9529. The device with installed SDK must be able to access this address. Note: Only one of datakitUrl and datawayUrl configurations should be selected
datawayUrl NSString Yes The URL address for reporting public Dataway. Obtain from [User Analysis Monitoring] application. Example: https://open.dataway.url, the device with installed SDK must be able to access this address. Note: Only one of datakitUrl and datawayUrl configurations should be selected
clientToken NSString Yes Authentication token, needs to be used together with datawayUrl
enableSDKDebugLog BOOL No Set whether debug logs are allowed. Default NO
env NSString No Set the collection environment. Default prod, supports customization, also can be set via the -setEnvWithType: method provided by FTEnv enumeration
service NSString No Set the name of the business or service it belongs to. Affects the service field data in Log and RUM. Default: df_rum_ios
globalContext NSDictionary No Add custom tags. Refer to the rules here
groupIdentifiers NSArray No Array of AppGroups Identifier corresponding to the Widget Extensions to be collected. If Widget Extensions data collection is enabled, App Groups must be configured and the Identifier must be set in this property
autoSync BOOL No Whether to automatically synchronize data to the server after data collection. Default YES. When set to NO, use [[FTMobileAgent sharedInstance] flushSyncData] to manage data synchronization manually
syncPageSize int No Set the number of entries per sync request. Range [5,]. Note: Larger request entry numbers indicate higher computational resource usage for data synchronization, default is 10
syncSleepTime int No Set the intermittent sync time. Range [0,5000], default not set
enableDataIntegerCompatible BOOL No Suggest enabling when coexisting with web data. This configuration handles storage compatibility issues for web data types
compressIntakeRequests BOOL No Compress uploaded synchronized data using deflate compression. Supported by SDK 1.5.6 and above versions, default disabled
enableLimitWithDbSize BOOL No Enable the feature to limit total cache size using DB.
Note: Once enabled, FTLoggerConfig.logCacheLimitCount and FTRUMConfig.rumCacheLimitCount will become ineffective. Supported by SDK 1.5.8 and above versions
dbCacheLimit long No DB cache limit size. Range [30MB,), default 100MB, unit byte, supported by SDK 1.5.8 and above versions
dbDiscardType FTDBCacheDiscard No Set the data discard rule for the database. Default FTDBDiscard
FTDBDiscard discards appended data when the number exceeds the maximum value. FTDBDiscardOldest discards old data when the number exceeds the maximum value. Supported by SDK 1.5.8 and above versions

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 Monitoring application ID. 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 applies to all View, Action, LongTask, Error data under the same session_id
enableTrackAppCrash BOOL No Set whether crash logs need to be collected. Default NO
enableTrackAppANR BOOL No Collect ANR events where the app freezes and does not respond. 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 threshold for UI freeze. Value range [100,), unit milliseconds, default 250ms. Supported by SDK 1.5.7 and above versions
enableTraceUserView BOOL No Set whether to track user View operations. Default NO
enableTraceUserAction BOOL No Set whether to track user Action operations. Default NO
enableTraceUserResource BOOL No Set whether to track user network requests. Default NO, only applicable to native http
Note: Network requests initiated via [NSURLSession sharedSession] cannot collect performance data;
SDK 1.5.9 and above support collecting network requests initiated through 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 collected crash data. FTErrorMonitorBattery for battery level, FTErrorMonitorMemory for memory usage, FTErrorMonitorCpu for CPU occupancy.
deviceMetricsMonitorType FTDeviceMetricsMonitorType No Performance monitoring type for views. Adds corresponding monitoring item information to collected View data. FTDeviceMetricsMonitorMemory monitors current application memory usage, FTDeviceMetricsMonitorCpu monitors CPU jump frequency, 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 by >= iOS 13.0 >= tvOS 13.0
globalContext NSDictionary No Add custom tags for distinguishing user monitoring data sources. If tracking functionality is needed, the parameter key should be track_id, value can be any numerical value. Refer to the notes here for adding rules
rumCacheLimitCount int No Maximum RUM cache size. Default 100_000, supported by SDK 1.5.8 and above versions
rumDiscardType FTRUMCacheDiscard No Set RUM discard rules. Default FTRUMCacheDiscard
FTRUMCacheDiscard discards appended data when RUM data count exceeds the maximum value. FTRUMDiscardOldest discards old data when RUM data count exceeds the maximum value. Supported by SDK 1.5.8 and above versions
resourcePropertyProvider FTResourcePropertyProvider No Add custom properties to RUM Resource via block callback. Supported by SDK 1.5.10 and above versions. Lower priority than URLSession Custom Collection

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 custom log output format
logLevelFilter NSArray No Set the status array of custom logs to be collected. Default collects all
enableLinkRumData BOOL No Whether to associate with RUM data. Default NO
globalContext NSDictionary No Add custom tags to logs. Refer to the rules here
logCacheLimitCount int No Local cache maximum log entry limit [1000,), larger logs indicate greater disk cache pressure, default 5000
discardType FTLogCacheDiscard No Set the log discard rule when log limit is reached. Default FTDiscard
FTDiscard discards appended data when log data count exceeds the maximum value (5000). FTDiscardOldest discards old data when log data count 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 agent related 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 Supports determining whether to perform custom link tracing via URLRequest, returns TraceContext upon confirmation, returns nil otherwise. Supported by SDK 1.5.10 and above versions. Lower priority than URLSession Custom Collection

RUM User Data Tracking

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

View

Usage Method

/// Create page
///
/// Call before the `-startViewWithName` method. This method records the page load time. If the load time cannot be obtained, this method can be omitted.
/// - Parameters:
///  - viewName: Page name
///  - loadTime: Page load 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;

/// Leave page
/// - Parameter property: Event custom attributes (optional)
-(void)stopViewWithProperty:(nullable NSDictionary *)property;
/// Create page
///
/// Call before the `-startViewWithName` method. This method records the page load time. If the load time cannot be obtained, this method can be omitted.
/// - Parameters:
///  - viewName: Page name
///  - loadTime: Page load 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]?)

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

Code Example

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

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

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

Action

Usage Method

/// Start RUM Action.
///
/// RUM binds the possible triggering Resource, Error, LongTask events of this Action. Avoid adding multiple times within 0.1 seconds. Only one Action is associated with the same View at the same time. New Actions will be discarded if the previous Action has not ended.
/// Adding Action with `addAction:actionType:property` method does not affect each other.
///
/// - 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
///
/// Adding Action with `startAction:actionType:property:` method does not affect each other.
/// - 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 binds the possible triggering Resource, Error, LongTask events of this Action. Avoid adding multiple times within 0.1 seconds. Only one Action is associated with the same View at the same time. New Actions will be discarded if the previous Action has not ended.
/// Adding Action with `addAction:actionType:property` method does not affect each other.
///
/// - 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
///
/// Adding Action with `startAction:actionType:property:` method does not affect each other.
/// - 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 Method

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

// Scene 1
[[FTExternalDataManager sharedManager] addErrorWithType:@"type" message:@"message" stack:@"stack"];
// Scene 2: Dynamic parameters
[[FTExternalDataManager sharedManager] addErrorWithType:@"ios_crash" message:@"crash_message" stack:@"crash_stack" property:@{@"custom_key":@"custom_value"}];
// Scene 3: Dynamic parameters
[[FTExternalDataManager sharedManager] addErrorWithType:@"ios_crash" state:FTAppStateUnknown message:@"crash_message" stack:@"crash_stack" property:@{@"custom_key":@"custom_value"}];
// Scene 1
FTExternalDataManager.shared().addError(withType: "custom_type", message: "custom_message", stack: "custom_stack")
// Scene 2: Dynamic parameters
FTExternalDataManager.shared().addError(withType: "custom_type", message: "custom_message", stack: "custom_stack",property: ["custom_key":"custom_value"])
// Scene 3: 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 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

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

Resource

Usage Method

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

// Step 2: Request completed
[[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 you can get the time data for each stage 
// FTResourceMetricsModel
// Get NSURLSessionTaskMetrics data directly using FTResourceMetricsModel's initializer for ios native
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 metrics pass nil
[[FTExternalDataManager sharedManager] addResourceWithKey:key metrics:metricsModel content:content];
// Step 1: Before the request starts
FTExternalDataManager.shared().startResource(withKey: key)

// Step 2: Request completed
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 you can get the time data for each stage 
// FTResourceMetricsModel
// Get NSURLSessionTaskMetrics data directly using FTResourceMetricsModel's initializer for ios native
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 metrics pass nil
FTExternalDataManager.shared().addResource(withKey: resource.key, metrics: metricsModel, content: contentModel)

Logger Log Printing

When initializing the SDK Log Configuration, configure enableCustomLog to allow custom log addition.

Current log content limit is 30 KB, exceeding characters will be truncated.

Usage Method

//
//  FTLogger.h
//  FTMobileSDK

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

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

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

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

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

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

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

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

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

Log Level

/// 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 Logs 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 debugging 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 log output;

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

[INFO]: Custom log level;

content: Custom log content;

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

You can enable automatic mode by configuring FTTraceConfig, or support users adding Trace-related data manually. Below are the related APIs for adding custom data:

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()
}

Intercepting URLSession Delegate for Custom Network Data Collection

The SDK provides a class FTURLSessionDelegate, which can be used to customize RUM Resource Collection and Tracing for network requests initiated by a specific URLSession.

  • FTURLSessionDelegate supports setting a traceInterceptor block to intercept URLResquest for custom tracing (supported by SDK 1.5.9 and above versions). The priority > FTTraceConfig.traceInterceptor.
  • FTURLSessionDelegate supports setting a provider block to collect additional properties needed for RUM Resources, with priority > FTRumConfig.resourcePropertyProvider.
  • When used together with FTRumConfig.enableTraceUserResource and FTTraceConfig.enableAutoTrace, the priority order is: 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 attributes, it's recommended to add a project abbreviation prefix to tag names, such as `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};
            };
// Supports custom trace, returns TraceContext upon confirmation, returns 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;
    };            
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:nil];
let delegate = FTURLSessionDelegate.init()
// Add custom RUM resource attributes, it's recommended to add a project abbreviation prefix to tag names, such as `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
        }
// Supports custom trace, returns TraceContext upon confirmation, returns nil otherwise  
delegate.traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }         
let session =  URLSession.init(configuration: URLSessionConfiguration.default, delegate:delegate 
, delegateQueue: nil)

Method Two

Inherit the delegate object from the 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 attributes, it's recommended to add a project abbreviation prefix to tag names, such as `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};
    };
        // Supports custom trace, returns TraceContext upon confirmation, returns 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;
       }; 
    }
    return self;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics{
    // Make sure to call the parent 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's recommended to add a project abbreviation prefix to tag names, such as `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
        }
        // Supports custom trace, returns TraceContext upon confirmation, returns nil otherwise
        traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
    }
    }

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

Method Three

Make the URLSession's delegate object conform to the FTURLSessionDelegateProviding protocol.

  • Implement the ftURLSessionDelegate property's get method in the protocol.
  • Forward the following URLSession's 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 attributes, it's recommended to add a project abbreviation prefix to tag names, such as `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};
            };
          // Supports custom trace, returns TraceContext upon confirmation, returns 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;
        }; 
    }
    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's recommended to add a project abbreviation prefix to tag names, such as `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
        }
        // Supports custom trace, returns TraceContext upon confirmation, returns nil otherwise
        ftURLSessionDelegate.traceInterceptor = { request in
            let traceContext = FTTraceContext()
            traceContext.traceHeader = ["trace_key":"trace_value"]
            traceContext.spanId = "spanId"
            traceContext.traceId = "traceId"
            return traceContext
        }
    }
    // 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

Usage Method

/// 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 current user
- (void)unbindUser;
/// 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 current user
open func unbindUser()

Code Example

// Call this method after successful user login to bind user information
[[FTMobileAgent sharedInstance] bindUserWithUserID:USERID userName:USERNAME userEmail:USEREMAIL extra:@{EXTRA_KEY:EXTRA_VALUE}];

// Call this method after user logout to unbind user information
[[FTMobileAgent sharedInstance] unbindUser];
// Call this method after successful user login to bind user information
FTMobileAgent.sharedInstance().bindUser(withUserID: USERID, userName: USERNAME, userEmail: USEREMAIL,extra:[EXTRA_KEY:EXTRA_VALUE])

// Call this method after user logout to unbind user information
FTMobileAgent.sharedInstance().unbindUser()

Closing the SDK

Use FTMobileAgent to close the SDK.

Usage Method

/// Close running objects within the SDK
+ (void)shutDown;
/// Close running objects within the SDK
open class func shutDown()

Code Example

// If dynamically changing the SDK configuration, need to close first to avoid generating erroneous data
[FTMobileAgent  shutDown];
// If dynamically changing the SDK configuration, need to close first to avoid generating erroneous data
FTMobileAgent.shutDown()

Clearing SDK Cache Data

Use FTMobileAgent to clear uncached data that has not been reported yet.

Usage Method

/// Clear all data that has not yet been uploaded to the server
+ (void)clearAllData;
/// Clear all data that has not yet been uploaded to the server
open class func clearAllData()

Code Example

[FTMobileAgent clearAllData];
FTMobileAgent.clearAllData()

Synchronizing Data Actively

Use FTMobileAgent to synchronize data actively.

When FTMobileConfig.autoSync = NO, manual data synchronization is required.

Usage Method

/// Actively synchronize data
- (void)flushSyncData;
/// Actively synchronize data
func flushSyncData()

Code Example

[[FTMobileAgent sharedInstance] flushSyncData];
// If dynamically changing the SDK configuration, need to close first to avoid generating erroneous data
FTMobileAgent.sharedInstance().flushSyncData()

Adding Custom Tags

Use FTMobileAgent to dynamically add tags during SDK runtime.

Usage Method

/// Add SDK global tag, applies to RUM, 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, 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])

Code Example

[FTMobileAgent  appendGlobalContext:@{@"global_key":@"global_value"}];
[FTMobileAgent  appendLogGlobalContext:@{@"log_key":@"log_value"}];
[FTMobileAgent  appendRUMGlobalContext:@{@"rum_key":@"rum_value"}];
let globalContext = ["global_key":"global_value"]
FTMobileAgent.appendGlobalContext(globalContext)
let rumGlobalContext = = ["rum_key":"rum_value"]
FTMobileAgent.appendRUMGlobalContext(rumGlobalContext)
let logGlobalContext = = ["log_key":"log_value"]
FTMobileAgent.appendLogGlobalContext(logGlobalContext)

Symbol File Upload

Adding Run Script in Xcode (Only supports 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 build phase, and set parameters like:<app_id>、<datakit_address>、<env>、<dataway_token>.

  3. Script: FTdSYMUpload.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. Needs to 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, you can search for FT_DSYM_ZIP_FILE in the script output logs to view the DSYM_SYMBOL.zip file path
FT_DSYM_ZIP_ONLY=0

If you need to upload symbol files for different environments, refer to the method below.

Multi-environment Configuration Parameters

Example: Use .xcconfig configuration file for multi-environment configuration

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

Create xcconfig configuration file method reference: Add a Build Configuration File to Your Project

// If using cocoapods, need to add the path of pods' .xcconfig to your .xcconfig file
// Import corresponding .xcconfig of pod
#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, you can check them 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. Attribute values: prod/gray/pre/common/local. Needs to 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 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
        .....
}

Detailed details can be referenced in the SDK Demo for multi-environment usage.

Running Terminal Script

Script: FTdSYMUpload.sh

Symbol File Upload (Only supports datakit [local deployment])

sh FTdSYMUpload.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 You can search for FT_DSYM_ZIP_FILE in the script output logs to view the Zip file path.

Parameter Explanation:

  • <datakit_address>: Address of the DataKit service, such as http://localhost:9529
  • <app_id>: Corresponds to RUM's applicationId
  • <env>: Corresponds to RUM's env
  • <version>: Application's 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 switches and file sharing Group Identifier for Widget Extension data collection, other configurations use the already set configurations in the main project SDK.

Field Type Required Explanation
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 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")

Meanwhile, 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 Collected Data from Widget Extensions

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

Usage Method

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

For WebView data monitoring, integrate the Web Monitoring SDK on the accessed page.

Custom Tag Usage Example

Compilation 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 for configuration 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 setup operations
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];

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

Runtime Read/Write File Method

Since RUM does not take effect after setting globalContext post-startup, users can save locally and retrieve the tag data when starting the application next time.

  1. Save files locally via NSUserDefaults, and add code to obtain tag data during SDK configuration.
NSString *dynamicTag = [[NSUserDefaults standardUserDefaults] valueForKey:@"DYNAMIC_TAG"]?:@"NO_VALUE";

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

SDK Runtime Addition

After initializing the SDK, use [FTMobileAgent appendGlobalContext:globalContext], [FTMobileAgent appendRUMGlobalContext:globalContext], and [FTMobileAgent appendLogGlobalContext:globalContext] to dynamically add tags. Once set, they will take effect immediately. Subsequently, the data reported by RUM or Log will automatically include the tag data. This usage method is suitable for scenarios where data retrieval is delayed, such as when tag data needs to be obtained via network requests.

// SDK initialization pseudocode, obtain
[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 usage of the SDK are consistent with iOS.

Note that tvOS does not support:

  • WebView data detection

  • FTRumConfig.errorMonitorType device battery monitoring

Common Issues

About Crash Log Analysis

In both Debug and Release modes during development, captured thread backtraces during a Crash are symbolized. However, the release package does not include the symbol table, so the key backtrace of the exception thread will only display the image name without converting to valid code symbols. The obtained crash log information consists of hexadecimal memory addresses and cannot locate the crashing code. Therefore, it's necessary to parse the hexadecimal memory addresses into corresponding classes and methods.

How to find dSYM files after compilation or packaging

  • In Xcode, dSYM files are usually generated along with the compiled .app file and located in the same directory.
  • If you archive the project, you can find it in Xcode's Window menu by selecting Organizer, then choosing the corresponding archive. Right-click on the archive and select Show in Finder. In Finder, locate the corresponding .xcarchive file. Right-click on the .xcarchive file, select Show Package Contents, then go into the dSYMs folder to find the corresponding dSYM file.

Why doesn't XCode generate dSYM files after compilation?

XCode Release builds default to generating dSYM files, while Debug builds default to not generating them. Corresponding Xcode settings 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 if bitCode is enabled?

When you upload 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, you need to download the corresponding dSYM file from the App Store for that version, then process and upload the symbol table file based on the input parameters using the script.
  • Do not integrate the script into the Target of the Xcode project, and do not 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 the locally compiled dSYM file, the deobfuscated results 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 file
Don’t include bitcode
Upload symbols
Retrieve via Xcode
Include bitcode
Upload symbols
Retrieve via iTunes Connect
Retrieve via Xcode, requires processing with .bcsymbolmap.
Include bitcode
Don’t upload symbols
Retrieve via Xcode, requires processing with .bcsymbolmap.
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, right-click on the corresponding archive, and choose the Show in Finder operation

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

  5. Select the dSYMs directory; the directory contains the downloaded dSYM files

Retrieve via iTunes Connect
  1. Login to App Store Connect;
  2. Enter "My Apps (My Apps)"
  3. In "App Store" or "TestFlight", choose a specific version and click "Build Metadata". On this page, click the button "Download dSYM (Download dSYM)" to download the dSYM file.
Process with .bcsymbolmap

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

Open the terminal and use the following command to process the obfuscation

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 prefix labels with a project abbreviation, such as df_tag_name. You can query source code for keys used in the project. When global variables in the SDK have the same names as those in RUM and Log, RUM and Log will override the global variables in the SDK.

Feedback

Is this page helpful? ×