iOS SDK - 数据隐私

遵守数据隐私法律

通过向 Singular 告知用户针对 GDPR、CCPA 及其他消费者隐私法规的同意选择,实施符合隐私要求的数据采集。

当用户同意或拒绝与第三方共享其信息时,使用 Singular 的隐私方法传达其选择。这可确保符合 California Consumer Privacy Act (CCPA) 等法规,并使合作伙伴能够尊重用户的隐私偏好。

了解更多: 有关 Singular 如何处理隐私同意的详细信息,请参阅 用户隐私和限制数据共享


限制数据共享

控制第三方数据共享

使用 limitDataSharing 方法告知 Singular 用户是否同意与第三方合作伙伴共享其个人数据。

方法签名:

+ (void)limitDataSharing:(BOOL)shouldLimitDataSharing;

参数:

  • NO (false): 用户已选择加入并同意共享其数据
  • YES (true): 用户已选择退出,不同意共享其数据

重要: 虽然此方法为可选,但会影响归因数据共享。某些合作伙伴仅在被明确告知用户已选择加入后才会共享完整的归因信息。

使用示例

Swift Objective-C
// User has opted in to share their data
Singular.limitDataSharing(false)

// User has opted out and declined to share their data
Singular.limitDataSharing(true)

// Example: Set based on user preference
func handlePrivacyConsent(userConsented: Bool) {
    // Pass inverse: false = opted in, true = opted out
    Singular.limitDataSharing(!userConsented)

    print("Data sharing: \(userConsented ? "Enabled" : "Limited")")
}

工作原理:

Singular 会在 用户隐私回调 中使用此设置,并将其传递给出于法规合规需要它的合作伙伴。


按事件级别限制数据共享

覆盖单一事件的数据共享

通过将 ATTRIBUTE_SNG_LIMIT_DATA_SHARING 归因与其他事件参数一起传递,可以为单个事件或自定义收入调用覆盖全局 limitDataSharing 设置。当用户在执行某项操作时同步更新同意,并希望该特定事件立即反映新的选择,而不更改后续事件的 SDK 全局设置时,这非常有用。

接受的值:

  • NO (false): 仅针对此事件用户选择加入
  • YES (true): 仅针对此事件用户选择退出

重要: 该归因必须为布尔值(Objective-C 中的 BOOL 、Swift 中的 Bool )。非布尔值输入将被忽略,请求将回退到全局 limitDataSharing 设置。无论哪种情况,在请求发送之前,该归因都会从事件参数中移除,因此永远不会出现在事件载荷中。

使用示例

Swift Objective-C
// Example 1: Standard event, opt out only for this event
var dic: [AnyHashable: Any] = [:]
dic[ATTRIBUTE_SNG_LIMIT_DATA_SHARING] = true
dic["order_id"] = "12345"

Singular.event("checkout_completed", withArgs: dic)

// Example 2: Custom revenue, opt in only for this event
var attributes: [AnyHashable: Any] = [:]
attributes[ATTRIBUTE_SNG_LIMIT_DATA_SHARING] = false
attributes["sku"] = "premium_monthly"

Singular.customRevenue("premium_purchase",
                      currency: "USD",
                      amount: 9.99,
                      withAttributes: attributes)

工作原理:

当提供有效的布尔值时,SDK 会从事件参数中移除该归因,并将其写入请求的 data_sharing_options.limit_data_sharing 字段 —— 仅对该单一请求覆盖全局 limitDataSharing 设置。全局设置和 getLimitDataSharing 的结果不会被修改,因此任何未提供该归因的后续事件将继续使用全局选择。


GDPR 合规方法

管理用户追踪同意并控制 SDK 功能,以遵守 GDPR(《通用数据保护条例》)和其他隐私法规。

追踪同意管理

trackingOptIn

通过向 Singular 服务器发送 GDPR 选择加入事件,记录用户明确的追踪同意。

Info.plist 前置要求: 如果您的应用使用 Apple 的 App Tracking Transparency(iOS 14.5+)—— 这是访问已同意用户的 IDFA 所必需的 —— 在显示 ATT 提示或调用 +trackingOptIn 之前,请在您的 Info.plist 中声明 NSUserTrackingUsageDescription ,并附上清晰的面向用户的说明。未声明此键即请求追踪的应用将在 App Review 中被拒绝。

方法签名:

+ (void)trackingOptIn;

使用时机:

  • GDPR 合规: 在 GDPR 监管区域内,当用户明确同意追踪时调用
  • 同意记录: 在 Singular 系统中将用户标记为已提供 GDPR 同意
  • 默认行为: 未调用此方法时,SDK 仍会继续追踪,但不会专门记录同意
Swift Objective-C
// User accepted tracking consent
Singular.trackingOptIn()

// Example: Call after consent dialog
func onUserAcceptedTracking() {
    Singular.trackingOptIn()
    print("User opted in to tracking")
}

追踪控制方法

stopAllTracking

完全禁用此设备上当前用户的所有 SDK 追踪活动。

方法签名:

+ (void)stopAllTracking;

重要警告: 在调用 resumeAllTracking 之前,此方法会永久禁用 SDK。即使应用重启,该禁用状态仍会保持,只能通过编程方式恢复。

行为:

  • 立即生效: 立即停止所有追踪、事件上报和数据采集
  • 持久状态: 即使应用关闭后重新打开,仍保持禁用
  • 无自动重置: 必须显式调用 resumeAllTracking 才能重新启用
Swift Objective-C
// User declined all tracking
Singular.stopAllTracking()

// Example: Handle user opt-out
func onUserDeclinedTracking() {
    Singular.stopAllTracking()
    print("All tracking stopped")

    // Optionally store preference
    saveUserTrackingPreference(false)
}

resumeAllTracking

在追踪被 stopAllTracking 停止后,重新启用追踪。

方法签名:

+ (void)resumeAllTracking;

使用场景:

  • 同意变更: 用户更改隐私偏好并重新选择加入追踪
  • 隐私设置: 用户通过应用设置菜单更新同意
  • 区域合规: 当用户移动到非监管区域时重新启用追踪
Swift Objective-C
// User opted back in to tracking
Singular.resumeAllTracking()

// Example: Handle consent update
func onUserResumedTracking() {
    Singular.resumeAllTracking()
    print("Tracking resumed")

    // Optionally update stored preference
    saveUserTrackingPreference(true)
}

isAllTrackingStopped

检查当前用户的追踪是否已被禁用。

方法签名:

+ (BOOL)isAllTrackingStopped;

返回值:

  • YES (true): 追踪当前已通过 stopAllTracking 停止
  • NO (false): 追踪处于活动状态(从未停止或已恢复)

如果 SDK 尚未初始化,无论之前的追踪状态如何,都会返回 NO 。在依赖此值之前,请始终确认 +start: 已完成,并将 NO 理解为"未停止或尚未初始化",而不是"正在主动追踪"。

Swift Objective-C
// Check current tracking status
let isTrackingStopped = Singular.isAllTrackingStopped()

// Example: Display privacy status in settings
func getPrivacyStatusText() -> String {
    return Singular.isAllTrackingStopped() ? "Tracking: Disabled" : "Tracking: Enabled"
}

// Example: Sync UI with tracking state
func updatePrivacyToggle() {
    let isStopped = Singular.isAllTrackingStopped()
    privacyToggle.isOn = !isStopped
    print("Current tracking state: \(isStopped ? "Stopped" : "Active")")
}

儿童隐私保护

trackingUnder13

告知 Singular 用户未满 13 岁,以遵守 COPPA(《儿童在线隐私保护法》)和其他儿童隐私法规。

方法签名:

+ (void)trackingUnder13;

合规要求:

  • COPPA 合规: 在美国采集 13 岁以下儿童数据的应用必须使用
  • 年龄限制内容: 当用户在注册或年龄验证时被识别为 13 岁以下时使用
  • 受限追踪: 限制数据采集以遵守儿童隐私保护法律
Swift Objective-C
// User identified as under 13
Singular.trackingUnder13()

// Example: Call after age verification
func onAgeVerified(userAge: Int) {
    if userAge < 13 {
        Singular.trackingUnder13()
        print("COPPA mode enabled for user under 13")
    }
}

重要: 在确定用户未满 13 岁后,请尽快调用此方法,最好在应用初始化时或年龄验证后立即调用。这可确保后续所有追踪均遵守儿童隐私法规。


limitAdvertisingIdentifiers

对于混合受众应用,在 SDK 初始化期间限制广告标识符(iOS 上的 IDFA)的采集和使用。

配置归因:

@property (assign) BOOL limitAdvertisingIdentifiers;

使用场景:

  • 混合受众应用: 在应用启动前已确定年龄、面向成人和儿童的应用
  • 已知 COPPA 用户: 初始化时即已知用户未满 13 岁
  • 隐私优先方式: 从第一个 SDK 会话起即应用广告标识符限制

配置方法

对于事先了解隐私要求的应用,在 SDK 初始化期间设置广告标识符限制。

Swift Objective-C
// Limit advertising identifiers at initialization
let config = SingularConfig(apiKey: "SDK_KEY", andSecret: "SDK_SECRET")
config?.limitAdvertisingIdentifiers = true

Singular.start(config)

Kids SDK 构建: limitAdvertisingIdentifiers 在 Singular Kids SDK 构建中被排除在编译之外(由 #ifndef SINGULAR_KIDS 守护)。如果您集成的是 Singular-Kids-SDK ,此归因不可用 —— Kids SDK 会自动执行更严格的标识符处理。要在运行时进行 COPPA 标记,请使用 trackingUnder13

运行时方法

+start: 之后的任何时间点切换广告标识符的采集。对于年龄关卡、同意提示或内容选择在 SDK 已启动后才发生的混合受众应用非常有用。

+ (void)setLimitAdvertisingIdentifiers:(BOOL)enabled;
Swift Objective-C
// Apply identifier limit after the user enters kids mode
func onKidsModeSwitched(isKidsMode: Bool) {
    Singular.setLimitAdvertisingIdentifiers(isKidsMode)
}

运行时方法同样需要 BOOL 参数 —— 不带参数调用 +setLimitAdvertisingIdentifiers 将无法编译。与配置时归因一样,此方法在 Kids SDK 构建中不可用。

最佳实践: 为获得全面的 COPPA 合规,请在初始化时使用 limitAdvertisingIdentifiers ,并在用户年龄经过验证时调用 trackingUnder13() 。这可确保从首次会话起就获得完整保护。


实施最佳实践

完整的隐私管理示例

实施全面的隐私控制,以尊重用户偏好并遵守相关法规。

Swift Objective-C
import Foundation

class PrivacyManager {

    private let userDefaults = UserDefaults.standard
    private let consentKey = "user_consent"
    private let dataSharingKey = "data_sharing"

    // Initialize privacy settings on app start
    func initializePrivacy() {
        let hasUserConsent = getUserConsent()
        let allowDataSharing = getDataSharingPreference()

        // Apply stored preferences
        if hasUserConsent {
            Singular.trackingOptIn()
            Singular.resumeAllTracking()
        } else {
            Singular.stopAllTracking()
        }

        // Set data sharing preference
        Singular.limitDataSharing(!allowDataSharing)

        print("Privacy initialized: consent=\(hasUserConsent), sharing=\(allowDataSharing)")
    }

    // User accepts tracking via consent dialog
    func userAcceptedTracking() {
        saveUserConsent(true)
        Singular.trackingOptIn()
        Singular.resumeAllTracking()
        print("User accepted tracking")
    }

    // User declines tracking
    func userDeclinedTracking() {
        saveUserConsent(false)
        Singular.stopAllTracking()
        print("User declined tracking")
    }

    // User updates data sharing preference
    func setDataSharingEnabled(_ enabled: Bool) {
        saveDataSharingPreference(enabled)
        // Note: limitDataSharing uses inverse logic
        Singular.limitDataSharing(!enabled)
        print("Data sharing: \(enabled ? "Enabled" : "Limited")")
    }

    // Get current tracking status
    func isTrackingEnabled() -> Bool {
        return !Singular.isAllTrackingStopped()
    }

    // Private helper methods
    private func getUserConsent() -> Bool {
        return userDefaults.bool(forKey: consentKey)
    }

    private func saveUserConsent(_ consent: Bool) {
        userDefaults.set(consent, forKey: consentKey)
    }

    private func getDataSharingPreference() -> Bool {
        return userDefaults.bool(forKey: dataSharingKey)
    }

    private func saveDataSharingPreference(_ enabled: Bool) {
        userDefaults.set(enabled, forKey: dataSharingKey)
    }
}

最佳实践:

  • 持久化存储: 将用户偏好保存到 UserDefaults 或安全存储中
  • 提前初始化: 尽可能在 SDK 初始化之前应用隐私设置
  • UI 同步: 使用 isAllTrackingStopped 保持设置 UI 与实际 SDK 状态同步
  • 清晰沟通: 在应用设置中提供清晰、易于访问的隐私控制
  • 合规文档: 维护清晰的隐私实现记录,以便用于法规审计