开始之前:SDK 先决条件
集成过程中出现问题? 请参阅下面的常见问题。
按照集成 Singular SDK 中的步骤操作:计划和先决条件》中的步骤。
这些步骤是任何 Singular SDK 集成的先决条件。
安装 SDK
您可以使用 CocoaPods、Swift 包管理器或静态库安装 Singular SDK。
-
在 Xcode 中,转到 "文件">"添加软件包",然后输入 Singular SDK GitHub 仓库:
https://github.com/singular-labs/Singular-iOS-SDK
-
更新 Singular SDK 版本:
- 单击 "Add Package(添加软件包)"按钮。
-
转到 "构建阶段"(Build Phases)>"二进制与库链接"(Link Binary with Libraries),然后添加AdServices.framework库。请务必将其标记为可选,因为它仅适用于 iOS 14.3 及更高版本的设备。
-
转到 "构建设置">"链接">"其他链接器标志",然后用以下项目更新 "其他链接器标志":
$(inherited) -ObjC -l "sqlite3.0" -l "z" -framework "AdSupport" -framework "Security" -framework "Singular" -framework "StoreKit" -framework "SystemConfiguration" -framework "WebKit" -framework "iAd" - 按照下面的说明创建Swift 桥接头。
只有在不使用上述 CocoaPods 或 SPM 方法的情况下,才需要下载并安装 Singular Framework。
您是从 Singular SDK 12.3.2 或更低版本升级的吗?
请按照以下步骤删除旧的静态库
- 导航到项目文件,删除 Singular 静态库。通常情况下,你应该有一个名为Singular-iOS-sdk-v12.3.2 的文件夹。右键单击该文件夹,将其从项目中删除。
- 前往下一节安装新框架
首次添加Singular框架
-
下载并解压 SDK 框架。
为您的实现选择正确的框架:
- 对于 Xcode 12 及以上版本,请使用.xcframework [在此下载]。
- 对于 Xcode 11 及以下版本,使用.framework [DOWNLOAD HERE]。
-
将解压后的文件夹添加到 Xcode 项目的文件夹中:
在 Xcode 中,右键单击 "您的应用程序名称">"添加文件到[您的项目名称]"。在打开的对话框中,选择选项 > 创建组,然后添加解压 SDK 的文件夹。
现在,Singular 框架应该就在你的项目中了。 -
添加所需的库:
- 在 Xcode 中,选择 "构建阶段"(Build Phases)>"二进制与库链接"(Link Binary With Libraries)。
-
点击+并添加以下库:
Libsqlite3.0.tbd SystemConfiguration.framework Security.framework Libz.tbd AdSupport.framework WebKit.framework StoreKit.framework AdServices.framework (mark as Optional since it's only available for devices with iOS 14.3 and higher).
- 嵌入并签署 Singular 框架
- 导航至常规 > 框架、库和嵌入式内容
- 将奇异框架调整为"嵌入和签名
如果你使用 CocoaPods 或 Swift 软件包管理器安装了 SDK,你必须为 Swift 创建一个桥接头,以便使用 Singular SDK 中的 Obj-C 库。
-
在项目中新建一个Header 类型 的文件,并将其命名为YourProjectName-Bridging-Header。
-
打开该文件并添加以下内容:
#import <Singular/Singular.h>
例如
-
转到 "构建设置">"Objective-C 桥接头",然后添加文件的相对路径:
集成 SDK
重要:
- 要使用 Swift,必须有桥接头(请参阅上述指南)。
- 如果使用 Swift 软件包管理器添加了 Singular SDK,请确保已按上述说明更新了 "构建设置"> "其他链接器标志"。
导入 Singular 库
在SceneDelegate、AppDelegate 或任何将使用 Singular 的文件中,导入 Singular 类库以开始使用 Singular SDK。
// If installed with Cocoapods or Swift Package Manager
import Singular
// If installed manually in Objective-C (New Framework)
#import <Singular/Singular.h>
// If installed manually in Objective-C (Legacy)
#import "Singular.h"
创建配置对象
在代码中初始化 Singular 功能之前,必须创建一个 Singular 配置对象并设置所有配置选项。该代码块应添加到 SceneDelegate 中(如果不使用 SceneDelegate,则添加到 AppDelegate 中)。
下面的代码示例创建了一个配置对象并设置了一些常用配置选项,如在托管模式下启用 SKAdNetwork 和设置等待 ATT 响应的超时时间。
以下各节将详细介绍这些选项以及如何对其进行自定义。
示例:使用一些常用选项创建配置对象
func getConfig() -> SingularConfig? {
// Create the config object with the SDK Key and SDK Secret
guard let config = SingularConfig(apiKey: 'APIKEY', andSecret: 'SECRET') else {
return nil
}
// If you are using App Tracking Transparency:
// Set a 300 sec delay before initialization to wait for
// the user's ATT response.
// (Remove this if you are not displaying an ATT prompt!)
config.waitForTrackingAuthorizationWithTimeoutInterval = 300
// Support custom ESP domains
config.espDomains = ["links.your-website-domain.com"]
// Set a handler method for deep links
config.singularLinksHandler = { params in
self.handleDeeplink(params: params)
}
return config
}
- (SingularConfig *)getConfig {
NSLog(@"-- Scene Delegate getConfig");
// Create the config object with the SDK Key and SDK Secret
SingularConfig* config = [[SingularConfig alloc] initWithApiKey:APIKEY
andSecret:SECRET];
// If you are using App Tracking Transparency:
// Set a 300 sec delay before initialization to wait for
// the user's ATT response.
// (Remove this if you are not displaying an ATT prompt!)
config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
// Support custom ESP domains
config.espDomains = @[@"links.your-website-domain.com"];
// Set a handler method for deep links
config.singularLinksHandler = ^(SingularLinkParams * params) {[self handleDeeplink:params];};
return config;
}
注:从 Singular iOS SDK 12.0.6 版开始,SKAdNetwork 默认已启用。
如果您仍在使用旧版本的 SDK,则需要在创建配置对象时使用以下代码启用 SKAdNetwork:
// Enable SKAdNetwork in Managed Mode
config.skAdNetworkEnabled = true
// Enable SKAdNetwork in Managed Mode
config.skAdNetworkEnabled = YES;
自定义 SKAdNetwork 选项
SKAdNetwork是 Apple 在不损害最终用户隐私的情况下确定移动安装归属的框架。SKAdNetwork 可让您在不共享用户个人身份信息的情况下衡量应用程序营销活动的绩效。
默认情况下,SKAdNetwork 在托管模式下启用,转换值由 Singular 从服务器端直接管理。如果使用托管模式,您无需在应用程序中添加任何代码来处理 SKAdNetwork。
这样可以最大限度地提高灵活性,因为您可以通过 Singular 平台设置和更改转换值,而无需修改客户端代码。
这种服务器端托管模式还能帮助您处理SKAdNetwork 定时器。SKAdNetwork 允许您在注册 SKAdNetwork 后 24 小时内更新转换值。任何更新转换值的调用都会将计时器延长 24 小时。因此,在选择转换事件时,您必须确保事件在该更新窗口内发生。在托管模式下,您可以随时更改转换事件配置,而无需发布应用程序的新版本。
在手动模式下使用 SKAdNetwork(高级)
如果您想使用应用程序代码自行更新转换值,首先必须在 Singular Config 中设置manualSkanConversionManagement标志。这样,您就可以使用多个 SDK 方法手动检索和更新转换值。
启用手动模式:
func getConfig() -> SingularConfig? {
// Singular Config Options
guard let config = SingularConfig(apiKey: Constants.APIKEY,
andSecret: Constants.SECRET) else {
return nil
}
//...
config.manualSkanConversionManagement = true
//...
return config
}
- (SingularConfig *)getConfig {
// Singular Config Options
SingularConfig* config = [[SingularConfig alloc] initWithApiKey:APIKEY
andSecret:SECRET];
//...
config.manualSkanConversionManagement = YES;
//...
return config;
}
更新转换值:
在手动模式下,要更新转换值,需要使用skanUpdateConversionValue方法。您可以在应用程序生命周期中任何需要的地方使用该方法。
注意:如果未启用 manualSkanConversionManagement,skanUpdateConversionValue 方法将不起作用。
skanUpdateConversionValue 方法 | |
---|---|
说明 | 手动更新 SKAdNetwork 转换值。 |
特征 | (BOOL)skanUpdateConversionValue:(NSInteger)conversionValue; |
使用示例 |
|
其他 SKAdNetwork 方法:
要获取当前转换值,请使用skanGetConversionValue 方法或conversionValueUpdatedCallback。这两种方法在托管和手动模式下均有效。
skanGetConversionValue 方法 | |
---|---|
方法描述 | 获取 Singular SDK 追踪的当前转换值。 |
特征 | (NSNumber *)skanGetConversionValue; |
使用示例 |
|
conversionValueUpdatedCallback 回调 | |
说明 | 获取 Singular SDK 追踪的当前转换值。 |
特征 | void(^conversionValueUpdatedCallback)(NSInteger); |
使用示例 |
|
处理 ATT 同意(设置初始化延迟)
显示 ATT(应用程序跟踪透明度)提示
从 iOS 14.5 开始,应用程序在访问和共享某些有助于追踪目的的用户数据(包括设备的IDFA)之前,必须征得用户同意(使用应用程序追踪透明度框架)。
拥有 IDFA 可以识别设备并执行安装归因,Singular 从中获益匪浅(不过也有不使用 IDFA 也能执行归因的方法)。我们强烈建议您征得用户同意获取 IDFA。
延迟初始化以等待 ATT 响应
默认情况下,Singular SDK 会在初始化时发送用户会话。当会话从新设备发送时,会立即触发 Singular 的归因流程--该流程仅根据 Singular 当时可用的数据执行。因此,在Singular SDK 发送第一个会话之前,必须征求同意并检索 IDFA。
要延迟启动用户会话,可在配置对象中使用waitForTrackingAuthorizationWithTimeoutInterval 选项初始化 Singular SDK。该选项已包含在2.2 中的代码示例中。创建配置对象。
func getConfig() -> SingularConfig? {
guard let config = SingularConfig(apiKey: Constants.APIKEY,
andSecret: Constants.SECRET) else {
return nil
}
//...
config.waitForTrackingAuthorizationWithTimeoutInterval = 300
//...
return config
}
- (SingularConfig *)getConfig {
SingularConfig* config = [[SingularConfig alloc] initWithApiKey:APIKEY
andSecret:SECRET];
//...
config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
//...
return config;
}
提示:设置初始化延迟后,应用程序的流程如下:
- 当应用程序打开时,Singular SDK 会开始记录会话和用户事件,但不会将其发送到 Singular 服务器。
- 当应用程序跟踪透明度同意被授予/拒绝,或设定的时间过去后,SDK 会将会话和任何排队的事件发送到 Singular 服务器(有或没有 IDFA)。
- 然后,Singular 会利用 IDFA(如果有的话)启动归属过程。
下表总结了使用此集成的可能方案:
方案 | IDFA 可用性 |
用户看到 "同意 "对话框,并在设定时间结束前授予 "同意"。 | IDFA 可用 |
用户看到同意对话框,并在设定时间结束前拒绝同意。 | IDFA 不可用 |
设定时间结束后,用户看到同意对话框并同意。 | IDFA 仅对同意后报告的用户事件有效 |
设置的时间已过,然后用户将看到同意对话框并拒绝同意。 | IDFA 不可用 |
用户看到同意对话框,未采取任何操作就退出了应用程序,然后在设定时间到期后打开应用程序并同意。 | 重新打开应用程序时,任何排队的事件都会发送到 Singular 服务器。IDFA 不能用于这些事件。同意后跟踪的任何事件都会有与之相关的 IDFA。 |
用户看到同意对话框后,未采取任何行动即退出应用程序,随后打开应用程序并拒绝同意。 | 重新打开应用程序时,任何排队的事件都会发送到 Singular 服务器。IDFA 不能用于这些事件或之后跟踪的任何事件。 |
初始化 Singular SDK
提示:在继续之前,请确保您已完成以下步骤!
- 添加 Singular 库
- 如果使用 swift:创建 Swift 桥接头
- 添加代码以创建 Singular 配置对象
- 添加深度链接处理程序
- 启用 SKAdNetwork
- 如果显示 ATT:添加了 waitForTrackingAuthorizationWithTimeoutInterval
- 测试成功构建应用程序(在此阶段,应用程序的构建应该没有错误)
每次打开应用程序时,都应初始化 Singular SDK。这是所有 Singular 归因功能的先决条件,它还会向 Singular 发送一个新的用户会话(会话用于计算用户保留率)。SDK 使用在创建配置对象(Creating a Configuration Object)中创建的配置对象进行初始化。
在哪里添加初始化代码?
你必须在应用程序的每个入口点初始化 Singular SDK:
-
对于使用 SwiftUI 界面且没有 SceneDelegate 或 AppDelegate 的 iOS 13+,请在以下ContentView().onOpenURL()和.onChange(of: scenePhase)中初始化 Singular SDK(示例代码见下文)。
-
对于iOS 13+,请在以下SceneDelegate函数中初始化 Singular SDK:willConnectTo session、continue userActivity、openURLContexts URLContexts。
-
对于不支持 SceneDelegate 的旧版本 iOS,请在以下AppDelegate函数中初始化 SDK:didFinishLaunchingWithOptions、continueUserActivity、openURL。
初始化代码示例
// INITIALIZE THE SDK IN THE FOLLOWING SCENEDELEGATE FUNCTIONS]
// willConnectTo session
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
let userActivity = connectionOptions.userActivities.first
// Print IDFV to Console for use in Singular SDK Console
print(Date(), "-- Scene Delegate IDFV:",
UIDevice().identifierForVendor!.uuidString as Any)
//Initialize the Singular SDK here:
if let config = self.getConfig() {
config.userActivity = userActivity
Singular.start(config)
}
}
// continue userActivity
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
// Starts a new Singular session on continueUserActivity
if let config = self.getConfig() {
config.userActivity = userActivity
Singular.start(config)
}
}
//openURLContexts URLContexts
func scene(_ scene: UIScene, openURLContexts URLContexts:
Set<UIOpenURLContext>) {
// Starts a new Singular session on cold start from deeplink scheme
if let config = self.getConfig() {
config.openUrl = openurlString
Singular.start(config)
}
// Add custom code here to Redirect to non-Singular deep links
//...
}
// INITIALIZE THE SDK IN THE FOLLOWING WINDOWGROUP FUNCTIONS
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
openURL = url
// Initialize Singular from an openURL
if let config = self.getConfig() {
config.openUrl = url
Singular.start(config)
}
})
}
.onChange(of: scenePhase) { oldValue, phase in
// The SwiftUI ScenePhases replaces the old SceneDelegate lifecycle events
switch phase {
case .background:
print("App Scene: backgrounded")
case .inactive:
print("App Scene: inactive")
case .active:
print("App Scene: active")
// Initialize Singular
if let config = self.getConfig() {
Singular.start(config)
}
@unknown default:
print("App Scene: unknown")
}
}
}
// INITIALIZE THE SDK IN THE FOLLOWING SCENEDELEGATE FUNCTIONS
// willConnectToSession
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
NSUserActivity* userActivity = [[[connectionOptions userActivities] allObjects]
firstObject];
// Print identifier for Vendor (IDFV) to Xcode Console for use in Singular SDK Console
NSLog(@"-- Scene Delegate IDFV: %@", [[[UIDevice currentDevice] identifierForVendor] UUIDString]);
// Start a new Singular session from a backgrounded app
SingularConfig *config = [self getConfig];
config.userActivity = userActivity;
[Singular start:config];
}
// continueUserActivity
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity{
// Starts a new Singular session from a backgrounded App
SingularConfig *config = [self getConfig];
config.userActivity = userActivity;
[Singular start:config];
}
// openURLContexts
- (void)scene:(UIScene *)scene openURLContexts:(nonnull NSSet *)URLContexts {
// Starts a new Singular session on cold start from deeplink scheme
SingularConfig *config = [self getConfig];
config.openUrl = url;
[Singular start:config];
// Add custom code here to Redirect to Non-Singular deep links
//...
}
// INITIALIZE THE SDK IN THE FOLLOWING APPDELEGATE FUNCTIONS
// didFinishLaunchingWithOptions
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Starts new session when user opens the app if session timeout passed/opened using Singular Link
SingularConfig *config = [self getConfig];
config.launchOptions = launchOptions;
[Singular start:config];
return YES;
}
// continueUserActivity
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id> *restorableObjects))restorationHandler {
// Starts a new session when the user opens the app using a Singular Link while it was in the background
SingularConfig *config = [self getConfig];
config.userActivity = userActivity;
[Singular start:config];
return YES;
}
// openURL
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options{
// Starts new session when user opens the app using a non-Singular link, like a traditional app scheme.
SingularConfig *config = [self getConfig];
config.openUrl = url;
[Singular start:config];
// Add custom code here to Redirect to non-Singular deep links
//...
return YES;
}
- AppDelegate 将仅在 iOS 12.4 或更早版本中初始化 Singular。
- 如果您的目标 iOS 版本高于 12.4,则应开始使用SceneDelegate活动。
// INITIALIZE THE SDK IN THE FOLLOWING APPDELEGATE FUNCTIONS
// didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool {
// Print IDFV to Console for use in Singular SDK Console
print(Date(), "-- Scene Delegate IDFV:", UIDevice().identifierForVendor!.uuidString as Any)
//Initialize the Singular SDK here:
if let config = self.getConfig() {
config.launchOptions = launchOptions
Singular.start(config)
}
return true
}
// continue userActivity
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) - Void) - Bool {
//Initialize the Singular SDK here:
if let config = self.getConfig() {
config.userActivity = userActivity
Singular.start(config)
}
return true
}
// open url
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) - Bool {
//Initialize the Singular SDK here:
if let config = self.getConfig() {
config.openUrl = url
Singular.start(config)
}
return true
}
注意事项
- 创建配置对象时,请注意传递正确的选项--userActivity 或 openUrl。请参阅下面的示例代码,并在需要时参考示例应用程序。
- 切记遵守您开展业务所在地区颁布的各种隐私法,包括 GDPR、CCPA 和 COPPA。如需了解更多信息,请参阅SDK 选择加入和选择退出实践,并查看可帮助您遵守数据隐私法的 Singular SDK 功能。
- 要初始化SDK,您需要SingularSDK Key和SDK Secret。您可以在Singular平台的"开发工具 > SDK集成 > SDK密钥"中获取。
常见问题
如果您在构建测试应用程序时遇到任何问题或错误,请参阅本节。
在 Xcode 15 中,有一个名为"User Script Sandboxing"(用户脚本沙盒)的新选项,它在构建过程中起着至关重要的作用。其目的是防止脚本对系统进行意外更改,从而增强构建的稳定性和安全性。启用后,构建系统会限制用户脚本,禁止未声明的输入/输出依赖关系。这对 Singular SDK 来说是个问题,因为它需要运行脚本来动态链接依赖关系。
要解决这个问题
- 导航至 "构建设置" > "构建选项"。
- 将"用户脚本沙盒"(User Script Sandboxing)调整为"否"。
- 检查是否创建了桥接头。
- 在"构建设置">"Objective-C 桥接头"中确认桥接头文件已链接。
为什么会出现日志错误?
以下常见日志错误可以忽略:
- [logging] duplicate column name: singular_link in "ALTER TABLE sessions ADD COLUMN singular_link TEXT DEFAULT NULL" 重复列名:singular_link
- [logging] "ALTER TABLE sessions ADD COLUMN payload TEXT DEFAULT NULL" 中的重复列名:payload
- [logging] "ALTER TABLE events ADD COLUMN sequence INTEGER DEFAULT -1" 中重复的列名:sequence
- [logging] "ALTER TABLE events ADD COLUMN payload TEXT DEFAULT NULL" 中的重复列名:payload