本文详细介绍用户体验监控 iOS SDK 的各功能接口,帮助您更灵活、深度地使用 SDK。
初始化接口
SDK 通过
TDEMConfiguration 配置初始化参数,调用 TDEM.start 完成初始化。TDEMConfiguration 除必填的 id 与 url 外,还提供一系列可选参数,详见 SDK 初始化。上下文与用户
TDEM.setUser("user-123") // 设置用户TDEM.clearUser() // 清除用户TDEM.setDeviceId("device-abc") // 设置设备标识,由宿主提供TDEM.clearDeviceId() // 清除设备标识,回落 not_setTDEM.setTags(["region": "ap-shanghai"]) // 批量设置标签TDEM.removeTags(["region"]) // 批量移除标签TDEM.clearTags() // 清空标签
[TDEM setUser:@"user-123"]; // 设置用户[TDEM clearUser]; // 清除用户[TDEM setDeviceId:@"device-abc"]; // 设置设备标识,由宿主提供[TDEM clearDeviceId]; // 清除设备标识,回落 not_set[TDEM setTags:@{ @"region": @"ap-shanghai" }]; // 批量设置标签[TDEM removeTags:@[@"region"]]; // 批量移除标签[TDEM clearTags]; // 清空标签
设备标识由业务侧提供,SDK 不会自动生成 IDFV 等系统标识;未设置或传空白时,上报字面量
not_set。也可在 TDEMConfiguration.deviceId 中一并设置。页面生命周期
TDEM.startPage(pageID: "checkout") // 进入逻辑页面(LIFO 入栈)TDEM.leavePage() // 离开逻辑页面(LIFO 出栈)
[TDEM startPageWithPageID:@"checkout"]; // 进入逻辑页面(LIFO 入栈)[TDEM leavePage]; // 离开逻辑页面(LIFO 出栈)
说明:
startPage / leavePage 需严格成对、按 LIFO 顺序调用。普通 UIKit 页面会自动检测,无需重复调用;SwiftUI 可在 onAppear / onDisappear 中配对调用。若页面切换不经过
viewDidAppear(例如 React Native 等 SPA 场景),可用 setPage 直接替换当前逻辑页面,无需与 leavePage 配对。该接口不依赖行为自动采集模块,调用后会立即上报一次 page_view(navigation_type 为 replace)。TDEM.setPage("checkout") // 替换当前逻辑页面TDEM.setPage("checkout", pageTitle: "结算页") // 同时指定页面标题
[TDEM setPage:@"checkout"]; // 替换当前逻辑页面[TDEM setPage:@"checkout" pageTitle:@"结算页"]; // 同时指定页面标题
自定义事件与测量
TDEM.track("checkout_completed", tags: ["step": "pay"], properties: ["order_type": "subscription"])TDEM.measure("api_latency", duration: 320, tags: ["api": "/pay"], properties: [:])TDEM.startMeasure("checkout_render") // 开始计时TDEM.endMeasure("checkout_render", tags: ["step": "pay"], properties: [:]) // 结束并上报TDEM.captureException(error, tags: ["module": "payment"], properties: [:]) // 上报已捕获异常TDEM.trackStruggle("dead_click", severity: .high, properties: ["element": "buy_btn"], tags: [:])
[TDEM trackWithName:@"checkout_completed" tags:@{ @"step": @"pay" } properties:@{ @"order_type": @"subscription" }];[TDEM measureWithName:@"api_latency" duration:320 tags:@{ @"api": @"/pay" }];[TDEM startMeasureWithName:@"checkout_render"]; // 开始计时[TDEM endMeasureWithName:@"checkout_render" tags:@{ @"step": @"pay" }]; // 结束并上报[TDEM captureExceptionWithError:error tags:@{ @"module": @"payment" }]; // 上报已捕获异常[TDEM trackStruggleWithName:@"dead_click" severity:TDEMStruggleSeverityHigh properties:@{ @"element": @"buy_btn" }];
启动与生命周期控制
TDEM.endLaunch() // 手动结束启动(需先设 launch.manualEndEnabled = true)TDEM.flush() // 立即冲刷缓冲事件TDEM.stop() // 停止 SDK
[TDEM endLaunch]; // 手动结束启动,返回 BOOL(需先设 launch.manualEndEnabled = true)[TDEM flush]; // 立即冲刷缓冲事件[TDEM stop]; // 停止 SDK
以上接口均需在 SDK 初始化 之后调用。未调用
TDEM.start 之前调用无效(返回 notRunning);若在 SDK 启动过程中调用,则会被缓冲排队,待就绪后按调用顺序上报。TDEM.setUser / setDeviceId / setTags 等上下文设置在启动前调用会先记录,同样在就绪后自动应用。隐私标记
通过
TDEMPrivacyMetadata 主动声明某个控件为敏感,用于自动识别覆盖不到的场景(例如订单金额、收货地址这类字符串本身无语义特征的文案)。import TDEMiOSSDK// 标记为敏感:行为监控不采集该控件文本,Session Replay 遮罩该控件及其子树TDEMPrivacyMetadata.setSensitive(true, for: orderAmountLabel)TDEMPrivacyMetadata.setSensitive(false, for: orderAmountLabel) // 取消标记// 只调 Session Replay 遮罩,不影响文本采集TDEMPrivacyMetadata.setReplayMasking(.masked, for: qrCodeView)TDEMPrivacyMetadata.setReplayMasking(.unmasked, for: qrCodeView)TDEMPrivacyMetadata.setReplayMasking(.inherit, for: qrCodeView) // 恢复默认判定
@import TDEMiOSSDK;// 标记为敏感:行为监控不采集该控件文本,Session Replay 遮罩该控件及其子树[TDEMPrivacyMetadata setSensitive:YES forView:orderAmountLabel];[TDEMPrivacyMetadata setSensitive:NO forView:orderAmountLabel]; // 取消标记// 只调 Session Replay 遮罩,不影响文本采集[TDEMPrivacyMetadata setReplayMasking:TDEMReplayMaskingMasked forView:qrCodeView];[TDEMPrivacyMetadata setReplayMasking:TDEMReplayMaskingUnmasked forView:qrCodeView];[TDEMPrivacyMetadata setReplayMasking:TDEMReplayMaskingInherit forView:qrCodeView]; // 恢复默认判定
TDEMReplayMasking 有三个取值:.inherit:默认,按下面的遮罩判定链走。.masked:强制遮罩。.unmasked:显式不遮罩。两条通道作用范围与继承语义不同:
通道 | 影响 | 对子控件是否继承 |
setSensitive | 行为监控文本采集 + Session Replay 遮罩 | 行为监控不继承,Session Replay 继承 |
setReplayMasking | 仅 Session Replay 遮罩 | 不继承 |
标记父容器时:Session Replay 会把标记向下传递,等价于遮罩整棵子树;但行为监控只判定被标记的那个控件本身,子控件文本仍会被采集,需逐个标记。
Session Replay 的遮罩判定按下列次序,命中即返回:
1. 不安全视图(类名含
camera / avplayerview):整帧跳过,不生成该帧回放。2. 强制遮罩(
forceMask):最高优先级,不可被任何标记撤销:setSensitive(true)(含父容器继承)、isSecureTextEntry、类名含 password / payment / authentication / safari / webview,以及 UITextField / UITextView 文本输入控件。3.
setReplayMasking(.masked) → 遮罩。4.
setReplayMasking(.unmasked) → 不遮罩。5.
maskAllText / maskAllImages 命中 → 遮罩。因此对已标记敏感的控件调用
setReplayMasking(.unmasked) 不会放开遮罩。与 Android 不同,iOS 没有
sentry-mask / sentry-unmask Tag 机制,也没有 unmaskViewClasses / maskViewClasses 配置项;强制遮罩来源为上述内置规则与业务标记。行为元数据
TDEMBehaviorMetadata 用于在自动识别拿不到稳定标识时,手动指定页面 ID、控件 ID,或把某个页面、控件整体排除在行为监控之外。该能力为 iOS 独有。import TDEMiOSSDK// 为页面显式指定 page_id(默认取控制器的完全限定类名)TDEMBehaviorMetadata.setPageID("checkout", for: checkoutViewController)TDEMBehaviorMetadata.setPageID(nil, for: checkoutViewController) // 撤销,回落自动识别// 为控件显式指定 element_id(优先级高于 accessibilityIdentifier)TDEMBehaviorMetadata.setElementID("buy_button", for: buyButton)TDEMBehaviorMetadata.setElementID(nil, for: buyButton)// 整个页面不产生行为事件(如隐私页面)TDEMBehaviorMetadata.setIgnored(true, for: privacyViewController)// 单个控件不产生行为事件TDEMBehaviorMetadata.setIgnored(true, for: adBannerView)// 点击仍上报,但不参与 dead_click(无响应点击)判定TDEMBehaviorMetadata.setDeadClickIgnored(true, for: refreshButton)// 标记业务反馈键,随 click / scroll 事件以 struggle_feedback_key 上报TDEMBehaviorMetadata.setStruggleFeedbackKey("coupon_refresh", for: refreshButton)
@import TDEMiOSSDK;// 为页面显式指定 page_id(默认取控制器的完全限定类名)[TDEMBehaviorMetadata setPageID:@"checkout" forViewController:checkoutViewController];[TDEMBehaviorMetadata setPageID:nil forViewController:checkoutViewController]; // 撤销,回落自动识别// 为控件显式指定 element_id(优先级高于 accessibilityIdentifier)[TDEMBehaviorMetadata setElementID:@"buy_button" forView:buyButton];[TDEMBehaviorMetadata setElementID:nil forView:buyButton];// 整个页面不产生行为事件(如隐私页面)[TDEMBehaviorMetadata setIgnored:YES forViewController:privacyViewController];// 单个控件不产生行为事件[TDEMBehaviorMetadata setIgnored:YES forView:adBannerView];// 点击仍上报,但不参与 dead_click(无响应点击)判定[TDEMBehaviorMetadata setDeadClickIgnored:YES forView:refreshButton];// 标记业务反馈键,随 click / scroll 事件以 struggle_feedback_key 上报[TDEMBehaviorMetadata setStruggleFeedbackKey:@"coupon_refresh" forView:refreshButton];
控件标识的解析优先级由高到低:
来源 | 说明 |
显式指定 | TDEMBehaviorMetadata.setElementID(_:for:) |
无障碍标识 | accessibilityIdentifier |
列表项 | UITableViewCell / UICollectionViewCell 的 table.<section>.<row>、collection.<section>.<item> |
稳定路径 | 从根视图起的子视图索引路径 |
类名兜底 | class:<类名> |
约束如下:
传入的字符串会去掉首尾空白,空字符串等同撤销设置;超过256字节会被截断。
页面 ID 未显式指定时,默认取控制器的完全限定类名;SwiftUI 的
UIHostingController 在未设置元数据时不产生页面事件。setIgnored 作用于页面时,该页面下的所有行为事件均不产生;作用于控件时只影响该控件本身。setDeadClickIgnored 只把控件排除在 dead_click(无响应点击)判定之外,点击事件本身仍会上报,并带上 dead_click_ignored 属性。setStruggleFeedbackKey 标记的键会随该控件的点击、滚动事件以 struggle_feedback_key 属性一并上报,便于在控制台把行为事件与业务反馈关联。会话与初始化监听
TDEM.currentSessionId() 返回当前分析会话 ID,SDK 未进入运行态时返回 nil。TDEM.addInitListener(_:) 注册启动结算监听,可以在 TDEM.start 之前调用;若注册时启动已完成,会立即用上次的结算结果回调一次。TDEM.removeInitListener(_:) 注销监听。final class InitLogger: TDEMInitListener {func onInitSettled(_ started: Bool, remoteConfig: TDEMRemoteConfigResult?) {print("started=\\(started), sampleRate=\\(remoteConfig?.tdemSampleRate ?? -1)")}}let logger = InitLogger()TDEM.addInitListener(logger)TDEM.removeInitListener(logger)let sessionId = TDEM.currentSessionId()// 判断调用结果if case let .rejected(error) = TDEM.endLaunch() {print(error.code, error.reason)}
@interface InitLogger : NSObject <TDEMInitListener>@end@implementation InitLogger- (void)onInitSettled:(BOOL)started remoteConfig:(TDEMRemoteConfigResult *)remoteConfig {NSLog(@"started=%d, sampleRate=%f", started, remoteConfig.tdemSampleRate);}@endInitLogger *logger = [InitLogger new];[TDEM addInitListener:logger];[TDEM removeInitListener:logger];NSString *sessionId = [TDEM currentSessionId];
TDEMRemoteConfigResult 携带远端配置快照:enabled:远端是否放开采集。tdemSampleRate:采样率。replaySampleRate:回放采样率,可能为 nil。需要判断调用结果时可参考
TDEMResult 与 TDEMError。TDEMResult 是 Swift 枚举(.accepted(eventID:) / .rejected(TDEMError)),Objective-C 不可见(OC 侧 endLaunch 直接返回 BOOL)。TDEMErrorCode 取值与含义:取值 | 含义 |
invalidConfiguration | 初始化配置非法 |
configurationConflict | 配置项之间相互冲突 |
notRunning | SDK 未处于运行态,调用被拒绝 |
invalidEvent | 事件未通过校验 |
storageFailure | 本地持久化失败,或启动期缓冲队列已满 |
internalFailure | SDK 内部错误 |
原始事件上报
TDEM.reportEvent(_:) 直接送入一条已成形的事件,用于 React Native 等薄桥接场景,SDK 不会对约定字段之外的字段做二次加工。TDEM.reportEvent(["eventType": "custom","eventCategory": "custom","timestamp": Int(Date().timeIntervalSince1970 * 1000),"data": ["event_name": "my_event"]])
[TDEM reportEvent:@{@"eventType": @"custom",@"eventCategory": @"custom",@"timestamp": @([[NSDate date] timeIntervalSince1970] * 1000),@"data": @{ @"event_name": @"my_event" }}];
字段说明:
eventType / eventCategory:事件类型与分类,缺省均为 custom,也兼容下划线写法 event_type / event_category。timestamp:事件发生时间(毫秒),缺省取当前时间。data:事件负载,其中 event_name 用作事件名。该接口仅在 SDK 运行态接受,未启动或已停止时调用会被拒绝(
notRunning)。