前提条件
在继续此集成之前,请完成 集成 Singular SDK:规划与前提条件 中的步骤。
重要: 这些前提步骤是任何 Singular SDK 集成的必要条件。
iOS 特定设置
在初始化 SDK 之前,请确认以下项目已在您的 Xcode 项目中配置妥当。缺少任何一项都是导致 SDK 静默失败的常见原因。
Info.plist 条目:
-
NSUserTrackingUsageDescription— 一段面向用户的字符串,说明您的应用为何请求跟踪权限。如果您曾向用户展示 App Tracking Transparency (ATT) 提示或读取 IDFA,Apple 要求必须配置此项。Singular SDK 会自动遵循 ATT;若未配置此键,SDK 仍可运行,但在 iOS 14.5+ 上无法访问 IDFA。 -
URL Types 和
CFBundleURLSchemes— 如果您的深度链接策略使用自定义 URI scheme(例如myapp://),则需要配置此项。通过 HTTPS 使用 Universal Links / Singular Links 时无需配置。
Entitlements (Signing & Capabilities):
-
Associated Domains
— 为每个 Singular 品牌域名添加一个条目,格式为
applinks:yourcompany.sng.link。Universal Link / Singular Link 归因需要此配置。这个 Singular 托管域名将托管一个apple-app-site-association(AASA) JSON 文件,Apple 会在安装时下载该文件。当在 Singular 平台为应用创建链接时,AASA 文件会自动生成和更新。 - Push Notifications — 卸载跟踪和推送归因再互动需要此项。请参阅"Supporting Push Notifications"和"Uninstall Tracking"文章。
所需系统框架(仅用于手动集成 — CocoaPods 和 SwiftPM 会自动链接):
-
StoreKit.framework— IAP 和 SKAdNetwork。 -
AdServices.framework— Apple Search Ads 归因令牌(iOS 14.3+)。 -
AppTrackingTransparency.framework— ATT 提示和授权状态(iOS 14.5+)。 -
UserNotifications.framework— 推送通知。 -
WebKit.framework— 仅当您使用 Singular WKWebView JS 桥接时才需要。
安装
选择您首选的安装方式。我们推荐大多数项目使用 CocoaPods。
快速决策:
- 已在使用 CocoaPods? 使用方法 1
- 仅使用 SPM 的项目? 使用方法 2
- 未使用任何包管理器? 使用方法 3
安装方法
方法 1:CocoaPods(推荐)
要求:
- 已安装 CocoaPods( 安装指南 )
- 可以通过终端访问您的项目目录
安装步骤:
-
初始化 Podfile (如果已有则跳过):
cd /path/to/your/project pod init -
添加 Singular SDK 到您的 Podfile:
platform :ios, '12.0' target 'YourAppName' do use_frameworks! # Singular SDK pod 'Singular-SDK' end -
安装依赖项:
pod install -
打开 workspace:
从现在起,打开
.xcworkspace而不是.xcodeproj - 仅 Swift 项目: 创建桥接头文件 (请参阅下方)
方法 2:Swift Package Manager
安装步骤:
- 在 Xcode 中: File → Add Packages
-
输入仓库 URL:
https://github.com/singular-labs/Singular-iOS-SDK - 选择版本并点击 Add Package
-
添加所需框架:
前往 Build Phases → Link Binary with Libraries 并添加:-
链接所需的库:
前往 Build Phases → Link Binary With Libraries 并添加:- Libsqlite3.0.tbd
- SystemConfiguration.framework
- Security.framework
- Libz.tbd
- AdSupport.framework
- WebKit.framework
- StoreKit.framework
- AdServices.framework(标记为 Optional)
-
链接所需的库:
- 仅 Swift 项目: 创建桥接头文件 (请参阅下方)
方法 3:手动 Framework 安装
适用场景: 仅在无法使用 CocoaPods 或 SPM 时使用此方法。
下载 Framework:
- Xcode 12+: 下载 .xcframework
- Xcode 11 及以下: 下载 .framework
安装步骤:
- 解压下载的 framework
- 在 Xcode 中:右键点击项目 → Add Files To [Project]
- 选择 Create Groups 并添加 framework 文件夹
-
链接所需的库:
前往 Build Phases → Link Binary With Libraries 并添加:- Libsqlite3.0.tbd
- SystemConfiguration.framework
- Security.framework
- Libz.tbd
- AdSupport.framework
- WebKit.framework
- StoreKit.framework
- AdServices.framework(标记为 Optional)
-
嵌入 framework:
前往 General → Frameworks, Libraries, and Embedded Content
将 Singular framework 设为 Embed & Sign
Swift 桥接头文件
重要: 使用 CocoaPods 或 SPM 的 Swift 项目需要此配置。
-
创建头文件:
Xcode → File → New → File → Header File
命名为YourProjectName-Bridging-Header.h -
添加 import:
#import <Singular/Singular.h> -
在 build settings 中链接:
Build Settings → Objective-C Bridging Header
设置为:YourProjectName/YourProjectName-Bridging-Header.h
配置并初始化 SDK
创建一个配置对象,并在应用的入口点初始化 SDK。
创建配置对象
基础配置
使用您的 SDK 凭据和可选功能创建一个
SingularConfig
对象。此配置适用于所有应用架构。
获取您的凭据: 在 Singular 平台的 Developer Tools → SDK Integration 下找到您的 SDK Key 和 SDK Secret。
// MARK: - Singular Configuration
private func getConfig() -> SingularConfig? {
// Create config with your credentials
guard let config = SingularConfig(
apiKey: "YOUR_SDK_KEY",
andSecret: "YOUR_SDK_SECRET"
) else {
return nil
}
// OPTIONAL: Wait for ATT consent (if showing ATT prompt)
// Remove this line if NOT using App Tracking Transparency
config.waitForTrackingAuthorizationWithTimeoutInterval = 300
// OPTIONAL: Support custom ESP domains for deep links
config.espDomains = ["links.your-domain.com"]
// OPTIONAL: Handle deep links
config.singularLinksHandler = { params in
if let params = params {
self.handleDeeplink(params)
}
}
return config
}
// MARK: - OPTIONAL: Deep link handler implementation
private func handleDeeplink(_ params: SingularLinkParams) {
// Guard clause: Exit if no deep link provided
guard let deeplink = params.getDeepLink() else {
return
}
// Extract deep link parameters
let passthrough = params.getPassthrough()
let isDeferred = params.isDeferred()
let urlParams = params.getUrlParameters()
#if DEBUG
// Debug logging only - stripped from production builds
print("Singular Links Handler")
print("Singular deeplink received:", deeplink)
print("Singular passthrough received:", passthrough ?? "none")
print("Singular isDeferred received:", isDeferred ? "YES" : "NO")
print("Singular URL Params received:", urlParams ?? [:])
#endif
// TODO: Navigate to appropriate screen based on deep link
// Add deep link handling code here. Navigate to appropriate screen.
}
#pragma mark - Singular Configuration
- (SingularConfig *)getConfig {
// Create config with your credentials
SingularConfig *config = [[SingularConfig alloc]
initWithApiKey:@"YOUR_SDK_KEY"
andSecret:@"YOUR_SDK_SECRET"];
// OPTIONAL: Wait for ATT consent (if showing ATT prompt)
// Remove this line if NOT using App Tracking Transparency
config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
// OPTIONAL: Support custom ESP domains for deep links
config.espDomains = @[@"links.your-domain.com"];
// OPTIONAL: Handle deep links
config.singularLinksHandler = ^(SingularLinkParams *params) {
[self handleDeeplink:params];
};
return config;
}
#pragma mark - OPTIONAL: Deep link handler implementation
- (void)handleDeeplink:(SingularLinkParams *)params {
// Guard clause: Exit if params is nil
if (!params) {
return;
}
// Guard clause: Exit if no deep link provided
NSString *deeplink = [params getDeepLink];
if (!deeplink) {
return;
}
// Extract deep link parameters
NSString *passthrough = [params getPassthrough];
BOOL isDeferred = [params isDeferred];
NSDictionary *urlParams = [params getUrlParameters];
#ifdef DEBUG
// Debug logging only - stripped from production builds
NSLog(@"Singular Links Handler");
NSLog(@"Singular deeplink received: %@", deeplink);
NSLog(@"Singular passthrough received: %@", passthrough);
NSLog(@"Singular isDeferred received: %@", isDeferred ? @"YES" : @"NO");
NSLog(@"Singular URL Params received: %@", urlParams);
#endif
// TODO: Navigate to appropriate screen based on deep link
// Add deep link handling code here. Navigate to appropriate screen.
}
SKAdNetwork 自动启用: 从 SDK 12.0.6 版本开始,SKAdNetwork 默认启用。无需额外配置。
初始化 SDK
选择您的应用架构
在每个应用入口点初始化 SDK。初始化模式取决于您应用的架构。
我使用的是哪种架构?
-
SceneDelegate:
检查您的项目是否有
SceneDelegate.swift或SceneDelegate.m -
SwiftUI:
您的应用以
@main struct YourApp: App开头 - 仅 AppDelegate: iOS 13 之前的应用或没有 SceneDelegate 的应用
现代 iOS:SceneDelegate(iOS 13+)
添加代码的位置:
SceneDelegate.swift
或
SceneDelegate.m
需要初始化的入口点:
-
willConnectTo session- 应用启动 -
continue userActivity- Universal Links -
openURLContexts- 深度链接 scheme
import Singular
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// 1️⃣ App launch
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// ANTI-SWIZZLING: Capture deep link parameters IMMEDIATELY before any
// other code runs. This prevents third-party SDKs from intercepting
// or modifying these values.
let userActivity = connectionOptions.userActivities.first
let urlContext = connectionOptions.urlContexts.first
let openUrl = urlContext?.url
#if DEBUG
// Log captured values to detect swizzling interference
print("[SWIZZLE CHECK] UserActivity captured:", userActivity?.webpageURL?.absoluteString ?? "none")
print("[SWIZZLE CHECK] URL Context captured:", openUrl?.absoluteString ?? "none")
print("IDFV:", UIDevice.current.identifierForVendor?.uuidString ?? "N/A")
#endif
// Create window from windowScene
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = UIViewController() // Replace with your root VC
window?.makeKeyAndVisible()
// Singular initialization - uses captured values to avoid swizzling conflicts
guard let config = getConfig() else { return }
// Pass Universal Link if available
if let userActivity = userActivity {
config.userActivity = userActivity
}
// Pass URL scheme if available
// CRITICAL for custom URL scheme attribution
if let openUrl = openUrl {
config.openUrl = openUrl
}
// Initialize Singular SDK
Singular.start(config)
}
// 2️⃣ Universal Links
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard let config = getConfig() else { return }
config.userActivity = userActivity
// Initialize Singular SDK
Singular.start(config)
}
// 3️⃣ Deep link schemes
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let config = getConfig() else { return }
if let url = URLContexts.first?.url {
config.openUrl = url
}
// Initialize Singular SDK
Singular.start(config)
}
}
#import <Singular/Singular.h>
@implementation SceneDelegate
// 1️⃣ App launch
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
// ANTI-SWIZZLING: Capture deep link parameters IMMEDIATELY before any
// other code runs. This prevents third-party SDKs from intercepting
// or modifying these values.
NSUserActivity *userActivity =
[[[connectionOptions userActivities]
allObjects] firstObject];
UIOpenURLContext *urlContext =
[[connectionOptions URLContexts]
allObjects].firstObject;
NSURL *openUrl = urlContext.URL;
#ifdef DEBUG
// Log captured values to detect swizzling interference
NSLog(@"[SWIZZLE CHECK] "
"UserActivity captured: %@",
userActivity.webpageURL);
NSLog(@"[SWIZZLE CHECK] "
"URL Context captured: %@",
openUrl);
#endif
// Create window from windowScene
UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
#ifdef DEBUG
// Print IDFV for testing in SDK Console
NSLog(@"IDFV: %@", [[[UIDevice currentDevice] identifierForVendor] UUIDString]);
#endif
// Singular initialization - uses captured values to avoid swizzling
SingularConfig *config = [self getConfig];
// Pass Universal Link if available
if (userActivity) {
config.userActivity = userActivity;
}
// Pass URL scheme if available
// CRITICAL for custom URL scheme
if (openUrl) {
config.openUrl = openUrl;
}
// Initialize Singular SDK
[Singular start:config];
}
// 2️⃣ Universal Links
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
// ANTI-SWIZZLING: Capture userActivity immediately at method entry. Called
// when Universal Link is opened while app is running or backgrounded.
NSUserActivity *capturedActivity = userActivity;
#ifdef DEBUG
NSLog(@"[SWIZZLE CHECK] "
"continueUserActivity captured: %@",
capturedActivity.webpageURL);
#endif
SingularConfig *config = [self getConfig];
config.userActivity = capturedActivity;
// Initialize Singular SDK
[Singular start:config];
}
// 3️⃣ Deep link schemes
- (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts {
// ANTI-SWIZZLING: Capture URL immediately at method entry. Called
// when URL scheme is opened while app is running or backgrounded.
NSURL *capturedUrl =
[[URLContexts allObjects]
firstObject].URL;
#ifdef DEBUG
NSLog(@"[SWIZZLE CHECK] "
"openURLContexts captured: %@",
capturedUrl);
#endif
SingularConfig *config = [self getConfig];
if (capturedUrl) {
config.openUrl = capturedUrl;
}
// Initialize Singular SDK
[Singular start:config];
}
@end
现代 iOS:SwiftUI App(iOS 14+)
添加代码的位置:
您的主
App
struct 文件
需要初始化的入口点:
-
.onOpenURL(of: scenePhase)- 处理自定义 URL scheme -
.onContinueUserActivity(of: scenePhase)- 处理 Universal Links(Singular Deep Links) -
.onChange.active- 处理首次启动时的初始化(如果没有发生深度链接)。处理 Deferred Deeplinks。
import SwiftUI
import Singular
@main
struct simpleSwiftUIApp: App {
@Environment(\.scenePhase) var scenePhase
@State private var hasInitialized = false
var body: some Scene {
WindowGroup {
ContentView()
// 1️⃣ Handle custom URL schemes (e.g., myapp://path)
.onOpenURL { url in
#if DEBUG
print("[Singular] URL Scheme:", url.absoluteString)
#endif
guard let config = getConfig() else { return }
config.openUrl = url
Singular.start(config)
hasInitialized = true
}
// 2️⃣ Handle Universal Links (e.g., https://links.your-domain.com)
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
#if DEBUG
print("[Singular] Universal Link:", userActivity.webpageURL?.absoluteString ?? "none")
#endif
guard let config = getConfig() else { return }
config.userActivity = userActivity
Singular.start(config)
hasInitialized = true
}
}
.onChange(of: scenePhase) { oldPhase, newPhase in
switch newPhase {
case .active:
// 3️⃣ Initialize ONLY on first launch if no deep link occurred
guard !hasInitialized else {
#if DEBUG
print("[Singular] Already initialized, skipping")
#endif
return
}
#if DEBUG
if let idfv = UIDevice.current.identifierForVendor?.uuidString {
print("[Singular] IDFV:", idfv)
}
#endif
guard let config = getConfig() else { return }
Singular.start(config)
hasInitialized = true
case .background:
#if DEBUG
print("[Singular] App backgrounded")
#endif
case .inactive:
#if DEBUG
print("[Singular] App inactive")
#endif
@unknown default:
break
}
}
}
// Add your getConfig() function here
// MARK: - Singular Configuration
private func getConfig() -> SingularConfig? {
// ... (same as above)
}
// Add your handleDeeplink() function here
// MARK: - Deep Link Handler
private func handleDeeplink(_ params: SingularLinkParams) {
// ... (same as above)
}
}
传统 iOS:AppDelegate(iOS 13 之前)
添加代码的位置:
AppDelegate.swift
或
AppDelegate.m
需要初始化的入口点:
-
didFinishLaunchingWithOptions- 应用启动 -
continue userActivity- Universal Links -
open url- 深度链接 scheme
import Singular
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
// 1️⃣ App launch
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ANTI-SWIZZLING: Capture deep link parameters IMMEDIATELY before any
// other code runs. This prevents third-party SDKs from intercepting
// or modifying these values.
let launchUrl = launchOptions?[.url] as? URL
let userActivityDictionary = launchOptions?[.userActivityDictionary] as? [String: Any]
let userActivity = userActivityDictionary?["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity
#if DEBUG
// Log captured values to detect swizzling interference
print("[SWIZZLE CHECK] Launch URL captured:", launchUrl?.absoluteString ?? "none")
print("[SWIZZLE CHECK] UserActivity captured:", userActivity?.webpageURL?.absoluteString ?? "none")
print("IDFV:", UIDevice.current.identifierForVendor?.uuidString ?? "N/A")
#endif
// Singular initialization - uses captured values to avoid swizzling conflicts
guard let config = getConfig() else { return true }
// Pass the entire launchOptions dictionary for Singular's internal processing
config.launchOptions = launchOptions
// Explicitly pass Universal Link if available
// CRITICAL for universal link attribution
if let userActivity = userActivity {
config.userActivity = userActivity
}
// Explicitly pass URL scheme if available
// CRITICAL for custom URL scheme attribution
if let launchUrl = launchUrl {
config.openUrl = launchUrl
}
// Initialize Singular SDK
Singular.start(config)
return true
}
// 2️⃣ Universal Links
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool {
#if DEBUG
print("[SWIZZLE CHECK] Universal Link handler called:", userActivity.webpageURL?.absoluteString ?? "none")
#endif
guard let config = getConfig() else { return true }
config.userActivity = userActivity
Singular.start(config)
return true
}
// 3️⃣ Deep link schemes
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
#if DEBUG
print("[SWIZZLE CHECK] URL Scheme handler called:", url.absoluteString)
#endif
guard let config = getConfig() else { return true }
config.openUrl = url
Singular.start(config)
return true
}
// Add your getConfig() function here
// MARK: - Singular Configuration
private func getConfig() -> SingularConfig? {
// ... (same as above)
}
// Add your handleDeeplink() function here
// MARK: - Deep Link Handler
private func handleDeeplink(_ params: SingularLinkParams) {
// ... (same as above)
}
}
#import "AppDelegate.h"
#import <Singular/Singular.h>
@implementation AppDelegate
// 1️⃣ App launch
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ANTI-SWIZZLING: Capture deep link parameters IMMEDIATELY before any
// other code runs. This prevents third-party SDKs from intercepting
// or modifying these values.
NSURL *launchUrl = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
NSDictionary *userActivityDictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity *userActivity = [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
#if DEBUG
// Log captured values to detect swizzling interference
NSLog(@"[SWIZZLE CHECK] Launch URL captured: %@", launchUrl.absoluteString ?: @"none");
NSLog(@"[SWIZZLE CHECK] UserActivity captured: %@", userActivity.webpageURL.absoluteString ?: @"none");
NSLog(@"IDFV: %@", [UIDevice currentDevice].identifierForVendor.UUIDString ?: @"N/A");
#endif
// Singular initialization - uses captured values to avoid swizzling conflicts
SingularConfig *config = [self getConfig];
if (!config) {
return YES;
}
// Pass the entire launchOptions dictionary for Singular's internal processing
config.launchOptions = launchOptions;
// Explicitly pass Universal Link if available
// CRITICAL for universal link attribution
if (userActivity) {
config.userActivity = userActivity;
}
// Explicitly pass URL scheme if available
// CRITICAL for custom URL scheme attribution
if (launchUrl) {
config.openUrl = launchUrl;
}
// Initialize Singular SDK
[Singular start:config];
return YES;
}
// 2️⃣ Universal Links
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
#if DEBUG
NSLog(@"[SWIZZLE CHECK] Universal Link handler called: %@", userActivity.webpageURL.absoluteString ?: @"none");
#endif
SingularConfig *config = [self getConfig];
if (!config) {
return YES;
}
config.userActivity = userActivity;
[Singular start:config];
return YES;
}
// 3️⃣ Deep link schemes
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
#if DEBUG
NSLog(@"[SWIZZLE CHECK] URL Scheme handler called: %@", url.absoluteString);
#endif
SingularConfig *config = [self getConfig];
if (!config) {
return YES;
}
config.openUrl = url;
[Singular start:config];
return YES;
}
#pragma mark - Singular Configuration
- (SingularConfig *)getConfig {
}
#pragma mark - OPTIONAL: Deep link handler implementation
- (void)handleDeeplink:(SingularLinkParams *)params {
}
@end
验证安装
上线前检查清单
在构建和测试您的集成之前,请确认以下事项。
- 已通过 CocoaPods、SPM 或手动 framework 方式安装 SDK
- 已创建 Swift 桥接头文件(如果使用 Swift)
-
已实现
getConfig()函数 -
已在所有入口点调用
Singular.start(config) - 已将 SDK Key 和 SDK Secret 添加到 config
- 已配置 ATT 超时(仅当显示 ATT 提示时)
- 已配置深度链接处理器(仅当使用深度链接时)
- 应用构建无错误
后续步骤:
- 构建并运行您的应用
- 在控制台中检查 IDFV 打印语句
- 使用您的 IDFV 在 Singular SDK Console 中进行测试
- 验证会话是否在 1-2 分钟内出现在 SDK Console 中
可选:App Tracking Transparency (ATT)
配置 ATT 以请求用户授权访问 IDFA,并提高归因准确性。
如果存在以下情况,请跳过本节: 您的应用未显示 ATT 提示。
为什么要请求 ATT 授权?
IDFA 的优势
从 iOS 14.5 开始,应用必须请求用户授权才能访问设备的 IDFA(Identifier for Advertisers,广告标识符)。
有 IDFA 与无 IDFA 的归因对比:
- 有 IDFA: 精准的设备级归因和准确的安装匹配
- 无 IDFA: 基于 IP、user agent 和设备指纹的概率性归因
建议: 请求 ATT 授权以获得更好的归因准确性。Singular 可以在没有 IDFA 的情况下进行归因,但准确性会降低。
配置 ATT 延迟
延迟 SDK 初始化
添加一个超时,以等待用户的 ATT 响应,然后再向 Singular 发送第一个会话。
关键: 在发送第一个会话之前,SDK 必须等待 ATT 授权。否则,初始归因事件将不包含 IDFA。
func getSingularConfig() -> SingularConfig? {
guard let config = SingularConfig(
apiKey: "YOUR_SDK_KEY",
andSecret: "YOUR_SDK_SECRET"
) else {
return nil
}
// Wait up to 300 seconds for ATT response
config.waitForTrackingAuthorizationWithTimeoutInterval = 300
return config
}
- (SingularConfig *)getSingularConfig {
SingularConfig *config = [[SingularConfig alloc]
initWithApiKey:@"YOUR_SDK_KEY"
andSecret:@"YOUR_SDK_SECRET"];
// Wait up to 300 seconds for ATT response
config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
return config;
}
ATT 流程时间线
当您配置 ATT 延迟后,会发生以下情况:
- 应用启动: SDK 开始记录事件,但暂不发送
- ATT 提示: 您的应用显示 ATT 授权对话框
- 用户响应: 用户同意或拒绝授权
- SDK 发送数据: SDK 立即发送已排队的事件(如已授权则包含 IDFA)
- 超时回退: 如果 300 秒后仍无响应,SDK 将无论如何发送数据
最佳实践: 尽早显示 ATT 提示(理想情况下在首次启动应用时),以最大化 IDFA 的可用性,提升归因效果。