iOS 基础 API
let sdk = try TrackBusinessSDK.create(endpoint: endpoint, projectKey: projectKey)
sdk.track("checkout_started")
try sdk.identify(userId: "user_1001")
sdk.screen("Checkout")iOS SDK 使用 Swift,支持类型化事件属性。
TIP
获取预编译 XCFramework 离线集成包(.xcframework.zip),请前往 iOS SDK 下载页。快速接入指南请参考 iOS 快速起步。
常用接口
| 模块类别 | 核心入口 (Swift) | 典型说明 |
|---|---|---|
| 初始化 | TrackBusinessSDK.create(...) | 可能抛出错误;在应用启动入口创建并复用实例 |
| 业务埋点 | sdk.track(...) / sdk.group(...) | 支持原生 Dictionary 或类型化 JSONValue 字典 |
| 位置快照 | import TrackBusinessLocation 后调用 trackWithLocation / grantLocationConsent / revokeLocationConsent | 可选模块,核心包不含该隐私声明;完整语义 |
| 手动 Flush | try await sdk.flush() | 手动触发持久化队列出队上报 |
| 身份管理 | identify / clearUser / switchUser / signOut | 首次关联、清除用户、切换账号、安全退出 |
| 身份快照 | let id = sdk.identity | 读取不可变的当前身份状态 |
| 用户属性 | sdk.setUserProperties(...) / profileIncrement | 提交 Profile 操作事件;接收端语义由部署实现决定 |
| 健康诊断 | sdk.getHealthSnapshot() / exportDiagnostics() | 查看运行状态与诊断报告;分享前检查宿主自定义内容 |
track() 返回不代表事件已写入队列或被接收端确认。仅含字符串的属性可使用 [String: String];其他类型使用 [String: JSONValue]。SDK 会合批发送,通常无需逐条调用 flush()。
1. SDK 初始化与生命周期
基础初始化 (TrackBusinessSDK.create)
以下示例使用下载包中的二进制模块 TrackBusinessSDKKit。
在 AppDelegate 或 @main App 中创建并持有 SDK 实例:
import Foundation
import TrackBusinessSDKKit
let sdk = try TrackBusinessSDK.create(
endpoint: URL(string: "https://collector.example.com/v1/events/batch")!,
projectKey: "ios-public-project-key"
)
sdk.track("checkout_started", properties: ["checkoutId": "chk_01"])常用初始化参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
endpoint | URL | 必填 | 事件接收地址,支持 HTTP 与 HTTPS |
projectKey | String | 必填 | 绑定项目环境的公开凭据 |
initialConsent | ConsentDecision | .granted(level: "MINIMAL") | .pending、.granted(level:) 或 .denied |
privacyPolicy | PrivacyPolicy | PrivacyPolicy() | 默认敏感字段保护及项目字段规则 |
flushSchedule | FlushSchedule | .default | 默认 20 条、前台 30 秒与退后台调度 |
beforeSend | EventInterceptor? | nil | 入队前处理事件属性或拒绝事件 |
policy | SDKPolicy | SDKPolicy() | 插件开关、白名单及其他受控本地策略 |
enrichmentPlugins | [any EventEnrichmentPlugin] | [] | 标准 create 与 createWithRemotePolicy 的命名属性增强器 |
identityService | String | "com.trackbusiness.sdk" | Keychain Service 基名,按项目配置派生隔离空间 |
identitySyncTransport | (any IdentitySyncTransport)? | nil | 可选身份同步适配器,需配套服务 |
backgroundSessionIdentifier | String? | nil | 选配系统后台会话;宿主保障可信最终地址无跳转,需转发回调;SDK 无法限制系统后台重定向 |
integrationWarningHandler | SDKIntegrationWarningHandler? | nil | 接收事件拒绝原因等元数据,不含属性、身份值或凭据 |
配置在创建实例时确定;完整参数见当前包的 create 声明。授权与敏感字段规则见授权与隐私。
命名业务属性增强
支持 event_enrichment 的 SDK 包可向标准工厂传入实现 EventEnrichmentPlugin 的 Swift 类型;通过 EventEnrichmentResult(context:properties:) 返回补丁,返回 nil 表示不修改,不删除事件。增强在最终隐私和事件计划检查前执行,不替换现有 beforeSend 的事件过滤职责。
列表按配置顺序处理,同名增强器保留第一个,同名业务属性由后者覆盖。身份、Session、eventId 和保留的 SDK Context 快照不可改写。默认补丁预算 16,384 bytes;异常或超限时保留原事件。回调是同步短任务,不能执行网络/磁盘等待,也不提供 Web 式异步强制超时。
注册不会提升 Consent;PENDING 或授权等级不足时不执行增强。完整注册、授权、触发及关闭配方见客户端插件与能力接入,旧包须先核对实际 API 和随包能力声明。
启动前异步远程策略 (createWithRemotePolicy)
当服务端已经部署并验证 GET /v1/sdk/policy,且宿主主动选择远程控制面时,可使用异步工厂加载插件策略或 Tracking Plan。它不是基础初始化或上传的前置条件;没有该接口时使用普通 create 和可选的本地 Tracking Plan。iOS 只在 SDK 创建前加载远程策略,运行期间不会热刷新;策略变化需由宿主重新创建 SDK 后生效。
let resolver = RemotePolicyResolver(
provider: URLSessionRemotePolicyProvider(
endpoint: endpoint,
projectKey: "ios-public-project-key"
),
cache: hostKeychainPolicyCache,
verifier: policyVerifier,
requireSignature: true // 生产环境开启验签
)
let sdk = try await TrackBusinessSDK.createWithRemotePolicy(
endpoint: endpoint,
projectKey: "ios-public-project-key",
remotePolicyResolver: resolver,
initialConsent: .granted(level: "STANDARD")
)WARNING
远程策略请求涉及前置网络交互,宿主必须确认处于权威的 GRANTED 状态;若处于 pending,将直接以 CONSENT_REQUIRED_BEFORE_REMOTE_POLICY 抛错终止。
实例释放 (close)
sdk.close()停止定时发送、网络恢复监听与生命周期观察。重复调用安全;已保存的身份和待发送事件仍会保留。
2. 身份识别与生命周期 (Identity)
核心身份流转 API
// 登录后绑定业务身份
try sdk.identify(userId: "user_9527")
// 退出登录
sdk.signOut()
// 只清除当前用户,保留匿名身份
sdk.clearUser()
// 切换账号
try sdk.switchUser(userId: "user_9528")
// 绑定其他业务标识
sdk.bindIdentity(namespace: "crm_id", value: "crm_1001")
// 重置身份和会话
sdk.reset()
// 获取当前身份
let identity = sdk.identityreset() 会重置用户与会话身份;安装身份按 SDK 语义保留。切换账号请使用 switchUser()。
3. 事件采集
SDK 在事件属性进入隐私过滤前限制嵌套层数、值数量和文本总量。采集属性或事件拦截器返回的属性超过处理预算时,SDK 丢弃该事件,诊断记录 invalid_event(event_input_budget)。后续事件继续处理。
业务事件与群组 (track / group)
// 1. 自定义业务埋点
sdk.track("checkout_started", properties: [
"checkoutId": .string("chk_10086"),
"estimatedTotal": .number(299.0),
"isPromotion": .boolean(false)
])
// 2. B2B 群组关联
sdk.group("org_alibaba", traits: [
"role": .string("admin"),
"tier": .string("enterprise")
])事件拦截回调的调用限制
设置全局属性或会话属性时,若更新后的总量超出 SDK 处理预算,本次更新会被拒绝,已有属性保持原值。健康快照记录 event_input_budget。
beforeSend、事件增强和动态公共属性回调在发起采集的线程上同步执行。回调应只做短小的内存操作,避免网络请求、磁盘读取和跨线程等待。回调执行期间,SDK 会拒收其他线程提交的事件,诊断记录 before_send_busy。回调结束后恢复正常准入。
beforeSend 中可以撤回授权、暂停采集、清理本地数据或关闭实例;这些操作会取消尚未提交的当前事件。close() 保留已经入队的事件。不要在该回调中等待队列写入、同步读取或确认队列:waitForPendingEventWrites() 等同步操作应在回调外调用。
4. 属性管理 (Super & User Properties)
Session Properties
仅保存在当前会话内存中,退出登录或会话超时后自动清理:
sdk.setSessionProperties(["campaign": .string("winter_sale")])
sdk.removeSessionProperties(["campaign"])Dynamic Super Properties
每次事件构建时同步求值,禁止在闭包中执行耗时锁或 I/O:
sdk.registerDynamicSuperProperties {
[
"currentTab": .string(getCurrentTabName()),
"unreadCount": .number(Double(getUnreadCount()))
]
}
sdk.clearDynamicSuperProperties()用户 Profile 操作
Profile API 提交操作事件,本地缓存与服务端画像可能不同步。服务端是否执行操作及如何保证并发更新,由部署实现决定。
setUserPropertyOnce由服务端判断目标属性是否已存在;profileAppend不保证数组去重或并发原子性。unsetUserProperties可指定未出现在本地缓存中的属性名。clearUserProperties按本地已知键产生$profile_unset,针对本地已知属性,不表示删除所有服务端数据。- 未授权、opt-out 或关闭实例后不执行 Profile 修改,也不在恢复授权后补发。禁止持久存储时仅维护内存缓存。
- 缓存读写问题通过诊断查看;操作返回不代表服务端更新完成。隐私擦除使用
denyConsent或clearLocalData,检查完成结果,清理失败时不能恢复采集。
// 1. 设置用户属性
sdk.setUserProperties(["plan": .string("Pro"), "level": .number(3)])
// 2. 仅首次有效设置
sdk.setUserPropertyOnce(["firstChannel": .string("app_store")])
// 3. 数值累加
sdk.profileIncrement(["loginCount": .number(1)])
// 4. 数组追加(单次最多 100 项,每个字符串最多 256 字符)
sdk.profileAppend(["badges": .array([.string("vip"), .string("early_adopter")])])
// 5. 删除指定属性,或清理本地已知的画像属性
sdk.unsetUserProperties(["legacy_tag"])
sdk.clearUserProperties()
// 6. 获取本地 Profile 字典快照
let profile = sdk.getUserProperties()业务 Item 维度属性
sdk.itemSet("product", itemId: "sku_998", properties: ["price": .number(99.0)])
sdk.itemDelete("product", itemId: "sku_998")5. 存储与发送
手动刷新与调度感知 (flush)
// 手动调度出队落盘与上报
try await sdk.flush()
// 前台定时发送默认已启用,仅在需要自行控制时调用
sdk.startForegroundFlush()
sdk.stopForegroundFlush()内存模式与事件调试接口
关闭持久存储时,事件仅保存在当前进程的内存队列。flush() 仍可尝试发送,进程结束后未发送事件无法恢复。
queuedEvents() 和 acknowledgeQueuedEvents(_:) 用于读取或确认本地调试队列;drainEvents() 仅排空 SDK 保留的内存事件快照,不能代替持久队列的 flush()。这些方法不撤回已发出的请求,也不表示 Collector 已接收事件。waitForPendingEventWrites() 是测试与诊断同步点;正常投递由 flush() 等待 SDK 的写入队列。
6. 调试、诊断与监控 (Diagnostics)
// 1. 获取健康度快照
let health = sdk.getHealthSnapshot()
// 2. 导出诊断报告(不含事件 properties;分享前仍应检查其中的元数据)
let bundle = sdk.exportDiagnostics()
// 3. 读取近期诊断时间线
let timeline = sdk.getDiagnosticsTimeline(limit: 50)
// 4. 读取最近入队的事件列表元数据
let recent = sdk.getRecentEvents()
// 5. 开发构建可在 create 时注册接入提示
let debugSDK = try TrackBusinessSDK.create(
endpoint: endpoint,
projectKey: projectKey,
integrationWarningHandler: { warning in
logger.warning("SDK event rejected: \(warning.code)")
}
)诊断报告可能包含事件 ID、事件名、插件名和错误定位信息。分享前请检查其中的业务自定义名称和文本。
7. 屏幕浏览与原生交互
页面停留与加载性能
// 1. 显式记录页面访问 ($screen)
sdk.screen("CheckoutViewController")
// 2. 页面加载完成耗时 ($page_load)
sdk.recordPageLoad(
pageViewId: "pv_checkout_01",
durationMs: 420,
screenName: "CheckoutViewController"
)控件点击与语义交互 (recordAppClick)
sdk.recordAppClick(
action: "checkout_submit",
targetTag: "button",
trackId: "btn-checkout-submit",
captureKey: "ios:uikit:checkout-submit",
elementType: "UIButton",
screenName: "CheckoutViewController"
)8. 平台原生能力与生命周期
应用生命周期自动采集 (TrackBusinessApplicationLifecycle)
如需自动采集 $app_start 与 $app_end,宿主可显式持有 UIKit 扩展的生命周期监听器:
import TrackBusinessSDKUIKit
let appLifecycle = TrackBusinessApplicationLifecycle(sdk: sdk)
appLifecycle.start()渠道来源快照
sdk.recordAttribution(campaign: ["utm_source": "newsletter", "utm_campaign": "spring"])
sdk.track("order_created", properties: ["orderType": "subscription"])仅需要已有首次/最近 UTM 来源时使用 recordAttribution。需要当前入口、会话入口和排除返回域时,见下方 recordAcquisition。
入口与营销触点(recordAcquisition,可选)
需要区分当前入口、会话入口和最近有效触点时使用。复用已初始化的 SDK,在宿主确认 STANDARD 授权及新入口事实之后调用:
sdk.recordAcquisition(AcquisitionInput(
parameters: ["utm_source": "newsletter"],
landingUrl: "https://shop.example.com/landing"
))
sdk.track("entry_viewed", properties: ["surface": "campaign_landing"])新输入被本地接受后,后续事件可携带 context.campaign.entry、sessionEntry 和 latestTouch。它们表示入口事实,不计算订单归因。原接口 recordAttribution 继续维护已有 UTM 视图,不要为同一入口重复调用两个接口。
Universal Link 与 Deep Link 采集
通过 TrackBusinessDeepLinkIntegration 统一处理 SceneDelegate、AppDelegate 与 SwiftUI 场景:
import TrackBusinessSDKUIKit
let deepLinks = TrackBusinessDeepLinkIntegration(
sdk: sdk,
options: TrackBusinessDeepLinkOptions(
allowedHTTPSHosts: ["links.example.com"],
allowedCustomSchemes: ["myapp"]
)
)
// SceneDelegate 接收链接
func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
contexts.forEach { deepLinks.capture(url: $0.url) }
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
deepLinks.capture(userActivity: userActivity)
}应用安装与更新
// 1. 显式记录应用安装(内部持久化去重)
sdk.recordApplicationInstall(properties: ["channel": .string("AppStore")])
// 2. 显式记录版本升级
sdk.recordApplicationUpdate(currentVersion: "2.1.0", properties: ["build": .string("20260901")])诊断信号转发
// 1. 业务非致命异常
sdk.recordNonFatalError(message: "OrderCheckoutFailure")
// 2. 启动与网络性能上报
sdk.recordStartupPerformance(durationMs: 320)
sdk.recordNetworkPerformance(durationMs: 180, resourceCount: 4, transferBytes: 12000, failedCount: 0)9. 元素曝光 (Exposure)
手动曝光 API (recordExposure)
sdk.recordExposure(
"feed_banner_01",
durationMs: 800,
visibleRatio: 0.75
)曝光事件支持附加业务属性;自定义属性名不要以 $ 开头,并遵循隐私过滤及事件大小限制。请确认接收服务支持对应事件契约。
UIKit View 与 CollectionView 绑定
监听器应作为视图控制器的属性持有,视图退出或复用时释放绑定。只调用 attach() 或 bind() 而不保留返回对象,会使监听器立即释放。
import TrackBusinessSDKUIKit
// 视图控制器属性
private var bannerExposure: ViewExposureTracker?
private var collectionExposure: UICollectionViewExposureBinder?
func bindExposure() {
unbindExposure()
bannerExposure = ViewExposureTracker.attach(
to: bannerImageView,
sdk: sdk,
exposureId: "home_hero_banner",
minDurationMs: 500,
minVisibleRatio: 0.5
)
collectionExposure = UICollectionViewExposureBinder(sdk: sdk) { indexPath in
"goods_item_\(indexPath.item)"
}.bind(collectionView)
}
func unbindExposure() {
bannerExposure?.detach()
bannerExposure = nil
collectionExposure?.unbind()
collectionExposure = nil
}10. 外部设备标识读取 (IDFA / IDFV)
ATT 授权检测与 IDFA 读取器
广告用途的 IDFA 读取由宿主管理 ATT 授权,读取器应检查授权状态:
import AdSupport
import AppTrackingTransparency
struct HostIDFAReader: DeviceIdentifierReader {
let name = "host-idfa-reader"
func read(purpose: DeviceIdentifierPurpose, consent: ConsentState) async -> DeviceIdentifierReading? {
guard purpose == .advertising else { return nil }
guard #available(iOS 14, *), ATTrackingManager.trackingAuthorizationStatus == .authorized else {
return DeviceIdentifierReading(
kind: .idfa,
status: .limited,
purpose: .advertising,
source: .host,
isUserResettable: true,
errorCode: "att_not_authorized"
)
}
return DeviceIdentifierReading(
kind: .idfa,
status: .available,
purpose: .advertising,
source: .host,
value: ASIdentifierManager.shared().advertisingIdentifier.uuidString,
isUserResettable: true
)
}
}IDFV 读取
若仅需应用供应商测量,可直接注入 TrackBusinessSDKUIKit 提供的 AppleVendorIdentifierReader:
import TrackBusinessSDKUIKit
let sdk = try TrackBusinessSDK.create(
endpoint: endpoint,
projectKey: projectKey,
deviceIdentifierReader: AppleVendorIdentifierReader()
)
let reading = await sdk.readDeviceIdentifier(.measurement)11. 预置上下文规范 (NativeContextCollector)
获准采集的事件可包含以下环境上下文。具体字段取决于设备和系统是否提供相关信息:
| 字段命名空间 | 包含属性 | 规范说明 |
|---|---|---|
context.device | manufacturer, model, osVersion, screenWidth, screenHeight, screenScale, orientation, locale, timezone, type | manufacturer 固定为 "Apple";屏幕宽高以 points 为单位;type 基于 UIUserInterfaceIdiom 判定为 phone / tablet / tv 等。 |
context.app | id, name, version, build | 自动提取宿主主 Bundle 的 Bundle ID、CFBundleDisplayName、CFBundleShortVersionString 与 CFBundleVersion。 |
context.network | online, connectionType | 网络类型(wifi / cellular / ethernet / none / other);SDK 不读取运营商名称或 WiFi SSID。 |
应用版本与构建观测
sdk.recordApplicationUpdate(currentVersion: "2.1.0", properties: ["currentBuild": .string("210")])SDK 会读取应用版本及构建号。显式调用时可通过 currentBuild 提供构建号。事件上的 $previous_version、$current_version、$previous_build、$current_build 和 $change_type 由 SDK 确定,不能通过自定义属性覆盖。
首次观测只建立基线。后续版本变化生成 version_changed,同版本的已知构建号变化生成 build_changed。SDK 不通过版本字符串排序判断升级或降级;缺失构建号不填零。内存模式下的基线不保证跨进程保留。
工厂作用域
同一 endpoint、projectKey 和 identityService 作用域只能持有一个活动 Runtime。重复创建会抛出 TrackBusinessSDKError.scopeAlreadyOwned(SDK_SCOPE_ALREADY_OWNED);宿主应复用已有实例,或等待 close() 完成后再创建。不同作用域可独立使用。自定义 storageDirectory 时,宿主负责确保目录归属正确。
SDK 计时器 startTimer/endTimer 的输出字段为 properties.$timer_duration_ms(非负毫秒数),宿主 durationMs 属性不被覆盖。计时事件的严格 Tracking Plan 需声明该测量字段。
有界投递验证
sdk.track("integration_check")
let report = await sdk.verifyDelivery(timeoutMs: 3000)
if report.status == "acknowledged" {
// 本次非空固定范围全部收到有效整批 ACK。
}verifyDelivery 不抛出网络/存储错误;通过 status/reason 表达本次观察结果。取消 Swift Task 只结束观察,已开始的上传仍保留原有所有权。未知计数为 nil,empty 不表示发送成功;该结果也不承诺后台实时发送、强杀后执行或报表可见。详见可靠投递。
Swift Callback 重载 verifyDelivery(timeoutMs:completion:) 会同步登记范围,并在主线程回调一次;RN/Flutter 原生桥使用此重载,避免先派发 Task 再确定目标的竞争。原生准入锁被其他事件占用时返回 busy,可稍后重试,不阻塞接入方线程。