iOS SDK 集成指南

 

集成过程中出现问题? 请参阅下面的常见问题。

开始之前:SDK 先决条件

按照集成 Singular SDK 中的步骤操作:规划和先决条件

这些步骤是任何 Singular SDK 集成的先决条件。

1.安装 SDK

您可以使用 CocoaPods、Swift 包管理器或静态库安装 Singular SDK。

使用CocoaPods安装 SDK
  1. 下载并安装最新版本的 CocoaPods。
  2. 创建 podfile,请在终端中导航至项目根文件夹,然后键入:

    pod init
  3. 添加 Singular SDK 依赖项,请在项目的 Podfile 中添加以下内容:

    pod 'Singular-SDK'

    例如

    pod_for_swiftcocoapods.png

  4. 在终端中,导航至项目根文件夹并运行:

    pod install
  5. 从这一点开始,打开 Xcode 工作区文件 .xcworkspace 来打开项目,而不是 .xcodeproj 文件。
  6. 根据以下说明创建 Swift 桥接头
使用Swift 软件包管理器安装 SDK
  1. 在 Xcode 中,转到 "文件">"添加软件包",然后输入 Singular SDK GitHub 仓库:

    https://github.com/singular-labs/Singular-iOS-SDK

    update_sdk_version_1.png

  2. 更新 Singular SDK 版本:

    update_sdk_version_2.png

  3. 单击 "Add Package(添加软件包)"按钮。
  4. 转到 "构建阶段"(Build Phases)>"与库链接二进制文件"(Link Binary withLibraries),然后添加AdServices.framework库。 请务必将其标记为 "可选",因为它仅适用于 iOS 14.3 及更高版本的设备。

    link_binary_adservices.png

  5. 转到 "构建设置">"链接">"其他链接器标志",然后用以下项目更新 "其他链接器标志":

    frameworks.png

    other_linker_flags.png

  6. 按照以下说明创建 Swift 桥接头
安装Singular SDK 框架(静态库)

只有在不使用上述 CocoaPods 或 SPM 方法的情况下,才需要下载并安装 Singular Framework

您是从 Singular SDK 12.3.2 或更低版本升级的吗?

请按照以下步骤删除旧的静态库

  1. 导航到项目文件,删除 Singular 静态库。通常情况下,你应该有一个名为Singular-iOS-sdk-v12.3.2 的文件夹。右键单击该文件夹,将其从项目中删除。
  2. 前往下一节安装新框架

首次添加Singular框架

  1. 下载并解压 SDK 框架。

    为您的实现选择正确的框架

    • 对于 Xcode 12 及以上版本,请使用.xcframework [在此下载] 。
    • 对于 Xcode 11 及以下版本,请使用.framework [在此下载] 。
  2. 将解压后的文件夹添加到 Xcode 项目的文件夹中:

    在 Xcode 中,右键单击 "您的应用程序名称">"添加文件到[您的项目名称]"。 在打开的对话框中,选择 "选项">"创建组",然后添加解压 SDK 的文件夹。





    现在,Singular 框架应出现在项目中

  3. 添加所需的库:

    • 在 Xcode 中,选择Build Phases > Link Binary With Libraries
    • 单击+并添加以下库:

      Libsqlite3.0.tbd
      SystemConfiguration.framework
      Security.framework
      Libz.tbd
      AdSupport.framework
      WebKit.framework
      StoreKit.framework
      AdServices.framework (标记为可选,因为它仅适用于 iOS 14.3 及更高版本的设备).
  4. 嵌入并签署奇异框架
    • 导航至常规 > 框架、库和嵌入式内容
    • 将 Singular 框架调整为 "嵌入和签名 "模式

添加 Swift 桥接头

如果使用 CocoaPods 或 Swift 包管理器安装了 SDK,则必须为 Swift 创建一个桥接头,以便使用 Singular SDK 中的 Obj-C 库。

  1. 在项目中新建一个Header 类型 文件,并将其命名为YourProjectName-Bridging-Header

    new_header_file.png

  2. 打开该文件并添加以下内容:

    #import <Singular/Singular.h>

    例如

    swift_cocoapods_import_singular.png

  3. 转到 "构建设置">"Objective-C 桥接头",添加文件的相对路径:

    objective_c_bridging_header.png

2.设置基本 SDK 集成

重要

  • 要使用 Swift,必须有桥接头(请参阅上述指南)。
  • 如果您使用 Swift 软件包管理器添加了 Singular SDK,请确保已按上述说明更新了 "构建设置 ">"其他链接器标志"。

2.1.导入 Singular 库

SceneDelegateAppDelegate任何将使用 Singular 的文件中,导入 Singular 类库以开始使用 Singular SDK。

// 如果使用 Cocoapods 或 Swift 软件包管理器安装
import Singular

// 如果在 Objective-C 中手动安装(新框架)
#import <Singular/Singular.h>

// 如果手动安装在 Objective-C(传统)中
#import "Singular.h"

2.2.创建配置对象

在代码中初始化 Singular 功能之前,必须创建一个 Singular 配置对象并设置所有配置选项。该代码块应添加到 SceneDelegate 中(如果不使用 SceneDelegate,则添加到 AppDelegate 中)。

下面的代码示例创建了一个配置对象设置了一些常用配置选项,如在托管模式下启用 SKAdNetwork 和设置等待 ATT 响应的超时时间。

以下各节将详细介绍这些选项以及如何对其进行自定义。

示例:使用一些常用选项创建配置对象

SwiftObjective-C
func getConfig() -> SingularConfig? {
            print(Date(), "-- Scene Delegate getConfig")
            // 使用 SDK 密钥和 SDK Secret 创建配置对象
            guard let config = SingularConfig(apiKey: Constants.APIKEY, andSecret:
            Constants.SECRET) else {
                return nil
            }
                
            /* 在初始化前设置 300 秒延迟,以等待用户的 ATT 响应。如果不显示 ATT 提示,请删除此项!*/
            config.waitForTrackingAuthorizationWithTimeoutInterval = 300
                
            // 支持自定义 ESP 域名
            config.espDomains = ["links.your-website-domain.com"]
                
            // 为深层链接设置处理程序方法
            config.singularLinksHandler = { params in
                self.handleDeeplink(params: params)
            }
                
            return config
        }

注意:从 Singular iOS SDK 12.0.6 版开始,SKAdNetwork 默认已启用。

如果您仍在使用旧版本的 SDK,则需要在创建配置对象时使用以下代码启用 SKAdNetwork:

SwiftObjective-C
// 在托管模式下启用 SKAdNetwork
config.skAdNetworkEnabled = true

2.3.自定义 SKAdNetwork 选项

SKAdNetwork是 Apple 在不损害最终用户隐私的情况下确定移动安装归属的框架。SKAdNetwork 可让您在不共享用户个人身份信息的情况下衡量应用程序营销活动的绩效。

默认情况下,SKAdNetwork 在托管模式下启用,转换值由 Singular 从服务器端直接管理。如果使用托管模式,您无需在应用程序中添加任何代码来处理 SKAdNetwork。

这样可以最大限度地提高灵活性,因为您可以通过 Singular 平台设置和更改转换值,而无需修改客户端代码。

这种服务器端托管模式还能帮助您处理SKAdNetwork 定时器。SKAdNetwork 允许您在注册 SKAdNetwork 后 24 小时内更新转换值。任何更新转换值的调用都会将计时器延长 24 小时。因此,在选择转换事件时,您必须确保事件在该更新窗口内发生。在托管模式下,您可以随时更改转换事件配置,而无需发布应用程序的新版本。

在手动模式下使用 SKAdNetwork(高级)

了解如何在手动模式下使用 SKAdNetwork

如果您想使用应用程序代码自行更新转换值,首先必须在 Singular Config 中设置manualSkanConversionManagement标志。这样,您就可以使用多个 SDK 方法手动检索和更新转换值。

启用手动模式

SwiftObjective-C
func getConfig() -> SingularConfig? {
            // 奇异配置选项
            guard let config = SingularConfig(apiKey: Constants.APIKEY,
                    andSecret: Constants.SECRET) else {
                return nil
            }
            ...
            config.manualSkanConversionManagement = true
            ...
            return config
        }

更新转换值

在手动模式下,要更新转换值,您需要使用skanUpdateConversionValue方法。您可以在应用程序生命周期中任何需要的地方使用该方法。

注意:如果未启用 manualSkanConversionManagement,skanUpdateConversionValue 方法将不起作用。

skanUpdateConversionValue 方法
说明 手动更新 SKAdNetwork 转换值。
特征 (BOOL)skanUpdateConversionValue:(NSInteger)conversionValue;
使用示例
SwiftObjective-C
// Sending a Standard Event for Login
      Singular.event(EVENT_SNG_LOGIN)
        
      // Manually updating the conversion value to 7 after the Event
      Singular.skanUpdateConversionValue(7)

其他 SKAdNetwork 方法

要获取当前转换值,请使用skanGetConversionValue 方法或conversionValueUpdatedCallback。这两种方法都适用于托管模式和手动模式。

skanGetConversionValue 方法
说明 获取 Singular SDK 追踪的当前转换值。
特征 (NSNumber *)skanGetConversionValue;
使用示例
SwiftObjective-C
let conversionValue = Singular.skanGetConversionValue()
conversionValueUpdatedCallback 回调
描述 获取由 Singular SDK 跟踪的当前转换值。
签名 void(^conversionValueUpdatedCallback)(NSInteger);
使用示例
SwiftObjective-C
func getConfig() -> SingularConfig? {
            // 奇异配置选项
            guard let config = SingularConfig(apiKey: Constants.APIKEY,
                andSecret: Constants.SECRET)
            else {         
                return nil     
            }     
            ...     
            config.conversionValueUpdatedCallback = { conversionValue in
                // 在这里,您可以获得最新的转换值
            }
            ...
            return config
        } 

2.4.处理 ATT 同意(设置初始化延迟)

显示 ATT(应用程序跟踪透明度)提示

从 iOS 14.5 开始,应用程序在访问和共享某些有助于跟踪目的的用户数据(包括设备的 IDFA)之前,必须征得用户同意( 使用应用程序跟踪)。

我们强烈建议您征得用户同意后获取IDFA

延迟初始化以等待 ATT 响应

默认情况下,Singular SDK 会在初始化时发送用户会话。因此,在 Singular SDK 发送第一个会 之前,必须征求用户同意并获取 IDFA。

要延迟用户会话的启动时间,可以在配置对象(Config)中使用waitForTrackingAuthorizationWithTimeoutInterval选项来初始化Singular SDK。 2.2 中的代码示例已经包含了该选项。创建配置对象。

SwiftObjective-C
func getConfig() -> SingularConfig? {
            guard let config = SingularConfig(apiKey: Constants.APIKEY,
                andSecret: Constants.SECRET) else {
                return nil
            }
            ...
            config.waitForTrackingAuthorizationWithTimeoutInterval = 300
            ...
            return config
        }

提示设置初始化延迟后,应用程序的流程如下:

  1. 当应用程序打开时,Singular SDK 会开始记录会话和用户事件,但不会将其发送到 Singular 服务器。
  2. 当应用程序跟踪透明度同意被授予/拒绝,或设定的时间过去后,SDK 会将会话和任何排队的事件发送到 Singular 服务器(带或不带 IDFA)。
  3. 然后,Singular 会利用 IDFA(如果有的话)启动归属过程。
了解所有可能的 ATT 场景

下表总结了使用此集成的可能方案:

方案 IDFA 可用性
用户看到 "同意 "对话框,并在设定时间结束前授予 "同意"。 IDFA 可用
用户看到同意对话框,并在设定时间结束前拒绝同意。 IDFA 不可用
设定时间结束后,用户看到同意对话框并同意。 IDFA 仅对同意后报告的用户事件有效
设置的时间已过,然后用户将看到同意对话框并拒绝同意。 IDFA 不可用
用户看到同意对话框,未采取任何操作就退出了应用程序,然后在设定时间到期后打开应用程序并同意。 重新打开应用程序时,任何排队的事件都会发送到 Singular 服务器。IDFA 不能用于这些事件。同意后跟踪的任何事件都会有与之相关的 IDFA。
用户看到同意对话框后,未采取任何行动就退出了应用程序,随后打开应用程序并拒绝同意。 重新打开应用程序时,任何排队的事件都会发送到 Singular 服务器。IDFA 不能用于这些事件或之后跟踪的任何事件。

2.5.处理深层链接

当用户在安装了应用程序的设备上点击深层链接时,应用程序会打开并显示特定的产品或体验。

Singular 跟踪链接可包括深度链接功能和延迟深度链接(更多信息请参阅我们的深度链接常见问题Singular 链接常见问题)。

上一步实施的 Singular SDK 配置引用了一个回调函数("handleDeeplink")。要通过 Singular SDK 启用深度链接和延迟深度链接支持,就必须使用 "handleDeeplink "函数。

实现深度链接的先决条件

确保已完成以下步骤:

  • 按照Singular Links Prerequisites 中的说明进行操作。
  • 在 Xcode 中,将 Singular 自定义子域添加到Signing & Capabilities > Associated Domains 中。
  • 在 "信息">"URL类型"中将应用程序方案添加到 URL类型
  • 在 Singular 平台的应用程序页面中添加 Apple DeveloperTeam ID方案

备注:

  • 如果应用程序已配置为使用 iOS 通用链接,则通用链接域已存在于关联域中,可以保留。该域应添加到 "支持的域 "配置选项中,如下一节所述。
  • 您还必须包含Singular 自定义链接域,以便 Singular 可以跟踪营销活动的归属,并处理这些营销活动的深度链接。

为处理程序创建回调方法

下面的代码示例创建了一个名为handleDeeplink的回调方法(上文的配置代码示例中引用了该方法)。

代码块签名为void(^)(SingularLinkParams*)SingularLinkParams包含深度链接目标直通参数以及链接是否延迟

SwiftObjective-C
func handleDeeplink(params: SingularLinkParams?) {
            // 从 Singular Link 获取深度链接数据
            let deeplink = params?.getDeepLink()
            let passthrough = params?.getPassthrough()
            let isDeferredDeeplink = params?.isDeferred()
            let urlParams = params?.getURLParameters()
            // 在此处添加深层链接处理代码
        }

其他链接选项

2.6.初始化 Singular

提示:在继续之前,请确保您已完成以下步骤!

  • 添加 Singular 库
  • 如果使用 swift:创建 Swift 桥接头
  • 添加了创建 Singular 配置对象的代码
  • 添加深度链接处理程序
  • 启用 SKAdNetwork
  • 如果显示 ATT:添加了 waitForTrackingAuthorizationWithTimeoutInterval
  • 测试成功构建应用程序(在此阶段,应用程序的构建应该没有错误

每次打开应用程序时,都应初始化 Singular SDK。 这是实现所有 Singular 归因功能的前提条件,它还会向 Singular 发送一个新的用户会话(会话用于计算用户留存)。 SDK 是使用在 2.2 中创建的配置对象初始化的。创建配置对象。

在哪里添加初始化代码?

你必须在应用程序的每个入口点初始化 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。

初始化代码示例

适用于 iOS 13+(Swift 场景代理)
// 在以下 scenedelegate 函数中初始化 sdk
// willConnectTo session
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let userActivity = connectionOptions.userActivities.first // 将 IDFV 打印到控制台,供 Singular SDK 控制台使用 print(Date(), "-- Scene Delegate IDFV:", UIDevice().identifierForVendor!.uuidString as Any)
// 在此初始化 Singular SDK: if let config = self.getConfig() { config.userActivity = userActivity Singular.start(config) } } // continue userActivity func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { // 通过 continueUserActivity 启动新的 Singular 会话 if let config = self.getConfig() { config.userActivity = userActivity Singular.start(config) } } //openURLContexts URLContexts func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { // 从深层链接计划冷启动时启动新的 Singular 会话 if let config = self.getConfig() { config.openUrl = openurlString Singular.start(config) }
// 在此处添加自定义代码,重定向至非星形深层链接 //... }
适用于 iOS 13+(SwiftUI 界面)
// 在以下窗口组函数中初始化 sdk
var body: some Scene { WindowGroup { ContentView() .onOpenURL(perform: { url in openURL = url // 从打开的 URL 初始化 Singular if let config = self.getConfig() { config.openUrl = url Singular.start(config) } }) } .onChange(of: scenePhase) { oldValue, phase in // SwiftUI ScenePhases 取代了旧的 SceneDelegate 生命周期事件 switch phase { case .background: print("应用程序场景:有背景") case .inactive: print("应用程序场景:不活动") case .active: print("应用程序场景:活动") // 初始化奇异 if let config = self.getConfig() { Singular.start(config) } @unknown default: print("应用程序场景:未知") } } }
适用于 iOS 13+(Objective-C 场景代理)
// 在以下 scenedelegate 函数中初始化 sdk
     
// willConnectToSession
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions { NSUserActivity* userActivity = [[[connectionOptions userActivities] allObjects]
firstObject]; // 将供应商标识符 (IDFV) 打印到 Xcode 控制台,供 Singular SDK 控制台使用 NSLog(@"-- Scene Delegate IDFV: %@", [[[UIDevice currentDevice] identifierForVendor] UUIDString]);
// 从后台应用程序启动新的 Singular 会话 SingularConfig *config = [self getConfig]; config.userActivity = userActivity; [Singular start:config]; } // continueUserActivity
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity{ // 从后台应用程序启动新的 Singular 会话 SingularConfig *config = [self getConfig]; config.userActivity = userActivity; [Singular start:config]; } // openURLContexts
- (void)scene:(UIScene *)scene openURLContexts:(nonnull NSSet *)URLContexts { // 从深层链接计划冷启动时启动新的 Singular 会话 SingularConfig *config = [self getConfig]; config.openUrl = url; [Singular start:config]; // 在此处添加自定义代码,重定向至非星形深层链接 //... }
适用于较旧的 iOS 版本(Objective-C AppDelegate)
// 在以下 appdelegate 函数中初始化 sdk
    
// didFinishLaunchingWithOptions
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 如果会话超时或使用奇异链接打开,则在用户打开应用时启动新会话 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 { // 当用户在后台使用奇异链接打开应用程序时,启动一个新会话 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{ // 当用户使用非星形链接(如传统的应用程序方案)打开应用程序时,启动新会话。 SingularConfig *config = [self getConfig]; config.openUrl = url; [Singular start:config]; // 在此处添加自定义代码,重定向至非星形深层链接 //... return YES; }

注意

  • 创建配置对象时,请注意传递正确的选项 - userActivity 或 openUrl。请参阅下面的示例代码,并在需要时参考示例应用程序。
  • 切记遵守您开展业务所在地区颁布的各种隐私法,包括 GDPR、CCPA 和 COPPA。如需了解更多信息,请参阅SDK 选择加入和选择退出实践,并查看可帮助您遵守数据隐私法的 Singular SDK 功能
  • 要初始化 SDK,您需要 Singular SDK 密钥和 SDK Secret。您可以在 Singular 平台的 "开发工具 > SDK 集成 > SDK 密钥 "中获取它们。

2.7.向 Singular 发送用户 ID(可选)

您可以使用 Singular SDK 方法向 Singular 发送内部用户 ID。

注:如果使用Singular 的跨设备解决方案,则必须在所有平台上收集用户 ID。

  • 用户 ID 可以是任何标识符,但不能暴露 PII(个人身份信息),例如,不能使用用户的电子邮件地址、用户名或电话号码。Singular 建议使用仅对第一方数据唯一的哈希值。
  • 传递给Singular的用户ID值也应该是你在所有平台(Web/移动/PC/控制台/离线)上获取的相同的内部用户ID。
  • Singular 将在用户级导出、ETL 和内部 BI 回传(如果配置)中包含用户 ID。用户 ID 是第一方数据,Singular 不会与其他方共享。
  • 使用 Singular SDK 方法设置的用户 ID 值将一直存在,直到使用unsetCustomUserId 方法取消设置或卸载应用程序为止。关闭或重启应用程序不会取消设置用户 ID。

要设置用户 ID,请使用setCustomUserId 方法。要取消设置(例如,如果用户 "注销 "账户),请致电unsetCustomUserId

注意:如果多个用户使用一台设备,我们建议实施注销流程,为每次登录和注销设置取消设置用户 ID。

如果已经知道应用程序打开时的用户 ID,请在初始化 Singular SDK 之前调用setCustomUserId 。这样,Singular 就能从第一次会话中获得用户 ID。不过,在用户注册或登录之前,用户 ID 通常是不可用的。在这种情况下,请在注册流程完成后调用setCustomUserId

setCustomUserId 和 unsetCustomUserId 方法
说明 设置和取消设置用户 ID。
签名 (void)setCustomUserId:(NSString*)customUserId
(void)unsetCustomUserId;
使用示例
SwiftObjective-C
setCustomUserId("custom_user_id")
unsetCustomUserId()
可选:自定义用户 ID 设备映射

重要:此高级企业功能仅在特殊情况下可用。实施前请咨询 Singular 解决方案工程师

Singular 可以通过服务器到服务器集成接收额外的移动事件跟踪数据。要使用此功能,必须将用户 ID 映射到 Singular 的移动设备跟踪标识符。

注意:在初始化 Singular SDK 后或获得用户 ID 后,请尽快调用此方法。

setDeviceCustomUserId 方法
说明 设置与登录相同的自定义用户 ID,并将其映射到 Singular 的跟踪标识符。
签名 static void setDeviceCustomUserId(String customUserId)
使用示例
SwiftObjective-C
setDeviceCustomUserId("custom_user_id")

2.8.实现全局属性(可选)

Singular SDK 可让您定义自定义属性,以便与应用程序发送的每个会话和事件一起发送到 Singular 服务器。这些属性可以代表有关用户、应用程序模式/状态或其他任何信息。

你最多可以定义 5 个全局属性。它们会在应用程序运行期间持续存在(使用您给定的最新值),直到您取消设置或用户卸载应用程序。

使用案例

全局属性的一些用例如下

  • 从第三方 SDK 传递一个标识符,然后将其从 Singular 回传给第三方以进行匹配。
  • 在游戏应用程序中,可以定义一个名为 "Level "的属性,并将其初始设置为 "0"。从应用程序发送的任何会话和事件都将以 "Level":"0 "的形式发送。一旦用户等级提高,就会将属性重置为 "1",以此类推。

注释

  • 全局属性目前反映在 Singular 的用户级事件日志(请参阅导出属性日志)和回传中。未来将在 Singular 的汇总报告(报告页面或报告 API)中添加对全局属性的支持。如果您对此功能有疑问,或对全局属性支持的更新感兴趣,请联系您的 Singular 客户成功经理。
  • 每个属性的名称和值最多可以有 200 个字符。如果传递的属性名称或值较长,将被截断为 200 个字符。

通过配置对象设置全局属性

要在初始化 SDK 之前设置全局属性,请使用 Config 对象中的setGlobalProperty方法。

请注意,由于全局属性及其值会在应用程序运行期间持续存在,因此您要设置的属性可能已被设置为不同的值。使用overrideExisting参数可告诉 SDK 是否用新值覆盖现有属性。

setGlobalProperty 方法
说明 设置全局属性。
特征 (void)setGlobalProperty:(NSString*)key withValue:(NSString*)value overrideExisting:(BOOL)overrideExisiting;
使用示例
SwiftObjective-C
 func getConfig() -> SingularConfig? {         
            /*(可选)获取要在全局属性中设置的第 3 方标识符。 如果第 3 方 SDK 向 Singular 提供任何标识符,则必须在 Singular 之前初始化相应的 SDK。*/
            let thirdPartyKey = "anonymousID"     
            let thirdPartyID = "2ed20738-059d-42b5-ab80-5aa0c530e3e1"     
        
            // 奇异配置选项    
            guard let config = SingularConfig(apiKey: Constants.APIKEY, 
                andSecret: Constants.SECRET)
            else {         
                return nil
            }     
            ...     
            // 使用单一全局属性功能捕获第三方标识符
            config.setGlobalProperty(thirdPartyKey, withValue:
                thirdPartyID, overrideExisting: true)     
            ...
            return config 
        }

初始化后设置全局属性

使用以下方法可在应用程序运行的任何时候设置、取消设置和检索全局属性。

注意

  • 如果属性尚未存在,且已有 5 个其他全局属性,则不会添加该属性。
  • 如果属性已被设置,overrideExisting 参数将决定是否覆盖现有值。
  • 如果属性设置成功,该方法返回 true,否则返回 false。
setGlobalProperty 方法
方法描述 将全局属性设置为给定值。
特征 (BOOL) setGlobalProperty:(NSString*)key andValue:(NSString*)value overrideExisting:(BOOL)overrideExisting
使用示例
SwiftObjective-C
var result = Singular.setGlobalProperty("propertyName",
            andValue: "propertyValue", overrideExisting: true)
获取全局属性方法
说明 以 Map 的形式读取所有全局属性及其当前值。
签名 NSDictionary*) getGlobalProperties
使用示例
SwiftObjective-C
var globalProperties = Singular.getGlobalProperties()
unsetGlobalProperty 方法
说明 删除全局属性。
签名 (void) unsetGlobalProperty:(NSString*)key
使用示例
SwiftObjective-C
Singular.unsetGlobalProperty("propertyName")
清除全局属性方法
说明 删除所有全局属性。
签名 (void) clearGlobalProperties
使用示例
SwiftObjective-C
Singular.clearGlobalProperties()

2.9.修改会话超时(可选)

默认情况下,如果应用程序在后台运行 60 秒或更长时间后才返回前台,SDK 将注册一个新会话。要更改超时值,请使用setSessionTimeout方法并将其添加到 Config 中。

setSessionTimeout 方法
说明 更改会话超时值。
签名 (void)setSessionTimeout:(int)timeout
使用示例
SwiftObjective-C
 func getConfig() -> SingularConfig? {       
              // 奇异配置选项    
              guard let config = SingularConfig(apiKey: Constants.APIKEY, andSecret:
                  Constants.SECRET) else {         
                  return nil     
              }     
              ...     
              Singular.setSessionTimeout(120)      
              ...     
              return config
          } 

3.跟踪事件和收入

注: 有关规划用户事件的详细信息,请参阅 SDK 规划和先决条件指南中的应用程序是否会跟踪用户事件?。

注:我们建议在应用程序中使用 Singular SDK 方法将所有应用程序内事件发送到 Singular 服务器。如果您计划从其他提供商或内部服务器发送事件到 Singular,请参阅下面的 "混合事件跟踪"部分。

3.1. 跟踪事件(无收入)

Singular可以收集应用程序内事件的相关数据,以帮助分析营销活动的效果并衡量关键绩效指标。例如,您的企业可能希望收集有关用户登录、注册、教程完成或游戏应用中等级提升的数据。

发送到 Singular 的事件列表(附带属性)应由用户体验/营销/业务团队根据营销关键绩效指标编制。

有关规划用户事件的更多详情,请参阅SDK 规划和先决条件指南中的应用程序是否会跟踪用户事件?

在代码中,使用eventeventWithArgs方法向 Singular 发送标准事件。

注:对于标准事件,请使用 iOS SDK 标准事件和属性列表 中的 iOS 事件 名称,如 EVENT_SNG_LOGIN

对于自定义事件,即贵组织希望测量的事件,如果与 Singular 的标准事件不匹配,可使用任何自定义名称(最多 32 个字符)。我们建议使用英文名称,以便与可能从 Singular 接收事件以进行优化的广告网络合作伙伴兼容。

事件 方法
说明 向 Singular 发送用户事件以进行跟踪。
签名 (void)event:(NSString *)name
使用示例
SwiftObjective-C
Singular.event(EVENT_SNG_LOGIN);
eventWithArgs 方法
说明 将用户事件发送给 Singular 以进行跟踪,并附带附加信息。
签名 (void)eventWithArgs:(NSString *)name,...
使用示例 下面的示例将发送带有推荐标准属性的内容视图事件。
SwiftObjective-C
dic[ATTRIBUTE_SNG_ATTR_CONTENT_TYPE] = "PrivacyController"
dic[ATTRIBUTE_SNG_ATTR_CONTENT_ID] = "0"
dic[ATTRIBUTE_SNG_ATTR_CONTENT] = "GDPR and CCPA Opt-Out Options"
Singular.event(EVENT_SNG_CONTENT_VIEW, withArgs: dic)

3.2.跟踪收入

Singular 可以收集通过应用程序获得的收入数据,以帮助分析营销活动的绩效和投资回报率。Singular会在报告、日志�����出和回帖中提供这些数据。

注:如果您的应用程序支持通过 App Store 进行 IAP 跟踪,建议使用此方法。如果您不使用 App Store IAP,请参阅下面的替代方法。

通过 IAP 跟踪报告收入(推荐)

要向 Singular 报告收入事件,请使用iapCompleteSDK 方法。该方法会向 Singular 发送 IAP(苹果应用内购买)收入事件:

  • 所有交易细节,Singular 将利用这些细节丰富报告内容。
  • 交易收据,可用于验证交易、分析或防止欺诈企图。

注意事项

  • 使用 iapComplete 时,必须在事件中 包含 SKPaymentTransaction 对象
  • 任何以不同货币报告的收入都将自动转换为贵组织首选的货币,具体货币在您的 Singular 账户中设置。
iapComplete 方法
说明 向 Singular 发送包含交易收据的收入事件。
签名 (void)iapComplete:(id)transaction (void)iapComplete:(id)transaction withName:(NSString *)name;
使用示例
SwiftObjective-C
// *** 获取 SKPaymentTransaction* 交易对象 ***
let transaction:SKPaymentTransaction = ...
          
// 将交易事件发送到 Singular,无需自定义事件名称
Singular.iapComplete(transaction)
          
// 使用自定义事件名称将交易事件发送到 Singular
Singular.iapComplete(transaction, withName:"MyCustomRevenue")

替代收入事件报告

虽然 Singular 推荐使用iapComplete,但我们也提供了两种向 Singular 报告收入事件的替代方法,以防您的应用程序不使用应用商店 IAP 跟踪。

收入自定义收入方法

使用revenuecustomRevenue方法,您可以手动指定交易金额和货币,以及可选的其他详细信息,如产品序列号和数量等。customRevenue方法还允许您传递自定义事件名称。

请注意,如果使用这些方法,Singular 不会获得交易收据,也无法验证交易。

注意:传递货币为三个字母的ISO 4217 货币代码,如 "USD"、"EUR"、"INR"。

收入 方法
说明 向 Singular 发送收入事件,包括收入金额、货币和可选的附加详细信息。
签名

(void)revenue:(NSString *)currency amount:(double)amount;

(void)revenue:(NSString *)currency amount:(double)amount productSKU:(NSString *)productSKU productName:(NSString *)productName productCategory:(NSString *)productCategory productQuantity:(int)productQuantity productPrice:(double)productPrice;

(void)revenue:(NSString *)currency amount:(double)amount withAttributes:(NSDictionary*)attributes;

使用示例
SwiftObjective-C
// 没有产品详细信息的收入
Singular.revenue("USD",amount:1.99)
          
// 包含产品详细信息的收入
Singular.revenue("EUR",amount:5.00, productSKU:"SKU1928375", 
  productName:"Reservation Fee",productCategory:"Fee", productQuantity:1, productPrice:5.00)
          
// 发送带有字典中属性的收入事件
var dic: [AnyHashable : Any] = [:]
dic[ATTRIBUTE_SNG_ATTR_ITEM_DESCRIPTION] = "100% Organic Cotton Mixed Plaid Flannel Shirt"
dic[ATTRIBUTE_SNG_ATTR_ITEM_PRICE] = "$69.95"
dic[ATTRIBUTE_SNG_ATTR_RATING] = "5 Star"
dic[ATTRIBUTE_SNG_ATTR_SEARCH_STRING] = "Flannel Shirt"
Singular.revenue("USD", amount: 19.95, withAttributes: dic)
自定义收入方法
说明 向 Singular 发送带有事件名称、收入金额、货币和可选附加详细信息的收入事件。
签名

(void)customRevenue:(NSString *)eventName currency:(NSString *)currency amount:(double)amount;

(void)customRevenue:(NSString *)eventName currency:(NSString *)currency amount:(double)amount productSKU:(NSString *)productSKU productName:(NSString *)productName productCategory:(NSString *)productCategory productQuantity:(int)productQuantity productPrice:(double)productPrice;

(void)customRevenue:(NSString*)eventname currency:(NSString *)currency amount:(double)amount withAttributes:(NSDictionary*)attributes;

使用示例
SwiftObjective-C
// 具有自定义名称且无产品详细信息的收入
Singular.customRevenue("MyCustomRevenue", currency:"USD", amount:1.99)
          
// 具有自定义名称和产品详细信息的收入
Singular.customRevenue("MyCustomRevenue", currency:"EUR", amount:5.00, productSKU:"SKU1928375",
  productName:"Reservation Fee", productCategory:"Fee", productQuantity:1, productPrice:5.00)
          
// 发送带有字典中属性的自定义收入事件
var dic: [AnyHashable : Any] = [:]
dic[ATTRIBUTE_SNG_ATTR_ITEM_DESCRIPTION] = "100% Organic Cotton Mixed Plaid Flannel Shirt"
dic[ATTRIBUTE_SNG_ATTR_ITEM_PRICE] = "$69.95"
dic[ATTRIBUTE_SNG_ATTR_RATING] = "5 Star"
dic[ATTRIBUTE_SNG_ATTR_SEARCH_STRING] = "Flannel Shirt"
Singular.customRevenue("CustomRevenueWithArgsDic", currency: "USD", amount: 44.99, withAttributes: dic)

3.3.混合事件跟踪(高级)

Singular 建议通过集成到应用程序中的 Singular SDK 发送所有事件和收入。不过,Singular 也可以从其他来源收集事件和收入。

任何非通过 Singular SDK 发送的事件都必须符合 Singular 的服务器到服务器事件文档要求,并提供匹配的设备标识符以正确归属事件。

重要

如果服务器到服务器事件请求中使用的设备标识符在 Singular 中没有匹配的设备标识符,就会出现差异。请注意以下可能性:

  • 如果事件请求是在 Singular SDK 从应用程序会话记录设备标识符之前收到的,那么该事件请求将被视为未知设备的 "第一次会话",Singular 将把该设备作为有机归属
  • 如果 Singular SDK 确实记录了设备标识符,但 Singular SDK 标识符与服务器到服务器事件请求中指定的设备标识符不同,那么事件将被错误归属

混合事件跟踪指南

从内部服务器发送事件

Singular可以收集来自服务器的收入数据,帮助分析营销活动的表现和投资回报率。

要求

  • 从应用内注册或登录事件中捕获并传递设备标识符,并将此数据与服务器上的用户 ID 一起存储。由于用户的设备标识符可能会发生变化,因此请确保在用户生成应用程序会话时更新标识符。这将确保服务器端事件归属于正确的设备。
  • 服务器端事件是平台特定的,只能使用与设备平台相匹配的设备标识符发送(如 iOS 设备的 IDFA 或 IDFV)。
  • 您可以使用 Singular 内部 BI 回传机制将事件实时推送到内部端点,以便更新服务器端的数据集。请参阅内部 BI 回传常见问题
  • 查看服务器到服务器集成指南中的"跟踪收入 "部分,了解详情。
从收入提供商发送事件
RevenueCatadapty等第三方提供商可以向Singular提供购买和订阅收入

请点击以下链接了解如何启用这些合作伙伴。

从分段发送事件

要使 Segment 能与 Singular SDK 并行发送事件到 Singular,必须在 Segment 中添加一个"云模式 "目的地。请点击此处查看我们的指南。

4.高级选项

4.1.创建短推荐人链接

注:此功能在 SDK 11.0.8+ 版本中可用。

使用createReferrerShortLink方法为用户生成简短的分享链接,以便与朋友分享应用程序。创建链接时,在应用程序代码中定义推荐用户的详细信息。这样就可以在报告中跟踪推荐人的属性。

创建简短链接

  1. 使用定义的深层链接创建一个奇异自定义源链接,该链接将引导您下载应用程序(请参阅奇异链接常见问题)。 该链接将在下面的代码中称为基本链接。
  2. 动态添加到链接的任何活动覆盖参数(选项列表请参阅跟踪链接参数)。
  3. 推荐用户的姓名和 ID,以便跟踪新应用的安装情况,并返回到共享链接的用户。

如下例所示,使用createReferrerShortLink方法生成短链接。

createReferrerShortLink 方法
说明 使用createReferrerShortLink方法生成简短的分享链接,供用户与朋友分享应用程序。
签名 (void)createReferrerShortLink:(NSString *)baseLink referrerName:(NSString *)referrerName referrerId:(NSString *)referrerId passthroughParams:(NSDictionary *)passthroughParams completionHandler:(void(^)(NSString *, NSError *))completionHandler;
使用示例
SwiftObjective-C
// 1. 定义referrer短链接变量
// 定义要用作基本链接的 Singular 跟踪链接:
let referrerBaseLink = "https://yourdomain.sng.link/Csfge/aknl?_dl=myscheme%3A%2F%2Fmydeeplink/referrer&_smtype=3";

// 添加您的推荐人 ID 和姓名 let referrerID = referrerIDField.text; let referrerName = referrerNameField.text;
// 自定义任何直通参数 let passthroughParams = ["channel": "sms"] // 2. 调用 Referrer ShortLink 让您的短链接在社交媒体上分享 Singular.createReferrerShortLink(referrerBaseLink, referrerName: referrerName, referrerId: referrerID,
passthroughParams: passthroughParams, completionHandler: {(shortLink, error) in if error != nil { // 根据错误原因重试/中止/修改传递给函数的参数的逻辑 } if (shortLink != nil || shortLink != "") { // 在这里添加您的分享逻辑: ... } })

4.2.添加广告收入归属支持

注:从 SDK 11.0.0 版开始,Singular 添加了通过 Singular SDK 设置广告收入归属的选项。广告收入归属仍可使用 API 调用进行设置,而无需更新应用程序中的 Singular SDK。但是,如果要衡量SKAdNetwork活动的广告收入,则需要通过 SDK 设置广告收入归因。

在Singular SDK中添加广告收入归因支持:

  1. 确保您使用的是最新版本的Singular SDK。
  2. 根据广告收入数据使用的中介平台,在您的Singular SDK集成中添加相应的代码片段。

注:以三个字母的 ISO 4217 货币代码传递货币,如 "USD"、"EUR"、"INR"。

AdMob
  1. 注: 此功能需要在您的 Admob 账户中启用。

    请参见https://support.google.com/admob/answer/11322405#getstarted

SwiftObjective-C
var rewardedAd: GADRewardedAd?
GADRewardedAd.load(withAdUnitID: "AD_UNIT_ID", request: request) { 
[weak self] ad, error in guard let self = self else { return } if let error = error { print("悬赏广告加载失败,出现错误: \(error.localizedDescription)") return } self.rewardedAd = ad self.rewardedAd?.paidEventHandler = { adValue in let impressionData = adValue let data = SingularAdData(
adPlatform: "Admob", currency: impressionData.currencyCode, revenue: impressionData.value) }
}
AppLovinMax
  1. 读取从 AppLovin MAX 事件中收到的对象,didReceivedMessage
SwiftObjective-C
if let message = message {
   let data = SingularAdData(
adPlatform: "AppLovin", currency: "USD", revenue: message.data["revenue"] as? Double ?? 0.0) Singular.adRevenue(data) }
IronSource
  1. 读取从 IronSource 的事件中收到的对象,impressionDataDidSucceed
  2. 确保打开 IronSource 中的 ARM SDK Postbacks 标志
  3. 请参见https://developers.is.com/ironsource-mobile/general/ad-revenue-measurement-postbacks/#step-1
SwiftObjective-C
if let impressionData = impressionData {
   let data = SingularAdData(
adPlatform: "IronSource", currency: "USD", revenue: impressionData.revenue) Singular.adRevenue(data) }
TradPlus
  1. 设置impressionDelegate
  2. TradPlusAdImpression 回调中添加单数
SwiftObjective-C
TradPlus.sharedInstance().impressionDelegate = self

func tradPlusAdImpression(_ adInfo: [AnyHashable: Any]?) { guard let adInfo = adInfo else { return } let currency = "USD" if let ecpm = adInfo["ecpm"] as? Float { let revenue = CGFloat(ecpm) / 1000.0 let data = SingularAdData(adPlatform: "TradPlus", currency: currency, revenue: revenue) Singular.adRevenue(data) }
}
其他(通用)
  1. 用相关数据初始化SingularAdData 对象
  2. 向 Singular 报告数据
SwiftObjective-C
let data = SingularAdData(
adPlatform: "您的广告平台", currency: "货币代码", revenue: 9.90) Singular.adRevenue(data)

4.3.跟踪卸载

注: 卸载跟踪仅适用于企业客户。此外,卸载跟踪要求应用程序支持推送通知。请参阅Apple 的 APNS 实施指南

配置卸载跟踪:

  1. 按照 "设置 iOS 安装跟踪"指南在 Singular 中启用应用程序。
  2. 在应用程序中,向 Singular 发送从苹果推送通知服务(APNS)返回的设备令牌。要将设备令牌传递给 Singular,请使用registerDeviceTokenForUninstallregisterDeviceToken方法。请在初始化 Singular SDK 之前执行此操作。该方法必须从 AppDelegatedidRegisterForRemoteNotificationsWithDeviceToken方法中调用。

注: 如果您已经从现有的推送通知实现中获取了设备令牌,则可以使用该值。

APNS 令牌通常是本地形式的二进制数据。传递从 APNS 收到的令牌。如果应用程序更改了令牌数据类型,则以十六进制编码字符串的形式传递,例如:b0adf7c9730763f88e1a048e28c68a9f806ed032fb522debff5bfba010a9b052

registerDeviceTokenForUninstall 方法
说明 传递从 APNS 返回的设备令牌。
签名 + (void)registerDeviceTokenForUninstall:(NSData*)deviceToken;
使用示例
SwiftObjective-C
func application(_ application: UIApplication, 
            didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
            // 将 deviceToken 发送到 Singular 以进行卸载跟踪     
            Singular.registerDeviceToken(forUninstall: deviceToken) 
        } 

4.4.Singular SDK JavaScript 接口

Singular 提供了一个 JavaScript 接口,您可以用它在应用程序中调用 Singular。

例如,如果设置了 JavaScript 接口,就可以通过 JavaScript 代码向 Singular 发送事件,如下所示:

JavaScript
Singular.event('event');
    Singular.event('test', JSON.stringify({"a1":"bar", "a2":"boo", "a3":"baz"}));

JavaScript 支持的方法

该接口支持以下 SDK 方法:

  • 设置客户用户 ID
  • unsetCustomUserID
  • 事件
  • 收入

启用 JavaScript 界面

注:从 iOS 8.0+ 开始,Apple 建议使用WKWebView为应用程序添加网页内容。请勿使用UIWebViewWebView。有关详细信息,请参阅Apple 的 WKWebView 文档

要在使用 WKWebView 时启用 JavaScript 接口,您需要在WKNavigationDelegate协议的webView 方法中添加一些代码(该协议可帮助您实现在 Web 视图处理导航请求时触发的自定义行为)。

Swift
extension ViewController: WKNavigationDelegate { 
        func webView(_: WKWebView, decidePolicyFor: WKNavigationAction, 
          decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 
          // 单一处理程序 
          let js = "typeof(Singular)" 
           webView.evaluateJavaScript(js) { (result, error) -> Void in 
             if let resultString = result as? String { 
               if resultString.isEqual("undefined") { 
                 do { 
                   let contents = try String(contentsOfFile: 
                   Bundle.main.path(forResource: "Singular", ofType: "js")!) 
                   self.webView.evaluateJavaScript(contents, completionHandler: nil) 
                 } catch { } 
               }
               else { 
                 print(decidePolicyFor.request) 
                 Singular.processJSRequestWK(self.webView, withURL:decidePolicyFor.request) 
               } 
             }  
           } 
           // 其余的代码放在这里 
        }
      }

5.遵守数据隐私法

Singular提供隐私保护功能,帮助您与任何可能遵守消费者隐私法(如GDPR和CCPA(加州消费者隐私法))的合作伙伴合作。 这些合作伙伴希望在最终用户同意共享其私人信息时得到通知。

5.1.限制数据共享

如果你已经实施了一种征求用户同意共享其信息的方法,请使用limitDataSharing方法通知 Singular 用户的选择:

  • 使用limitDataSharing:NO表示用户同意(选择加入)共享其信息。
  • 如果用户不同意,则使用 limitDataSharing:YES

Singular在"用户隐私回传"中使用限制数据分享,并将此信息传递给需要的合作伙伴,以遵守相关法规。更多信息请参阅"用户隐私和限制数据共享"

注意:该方法的使用是可选的,但可能会有归属信息,只有在明确通知用户已选择的情况下,合作伙伴才会与Singular共享这些信息。

限制数据共享方法
说明 通知 Singular 用户同意(选择加入)共享私人数据。
签名 (void)limitDataSharing:(BOOL)shouldLimitDataSharing;
使用示例
SwiftObjective-C
// 用户已选择分享
data Singular.limitDataSharing(false) 

5.2.遵守 GDPR 的其他方法

Singular SDK 提供了多种方法来帮助您遵守 GDPR 政策,并让 Singular 了解用户是否同意跟踪。

trackingOptIn 方法
说明 通知 Singular 用户同意跟踪(选入)。
签名 (void)trackingOptIn;
使用示例
SwiftObjective-C
Singular.trackingOptIn() 
stopAllTracking 方法
说明 停止此用户在此应用程序上的所有跟踪活动。
签名 (void)stopAllTracking;
使用示例

重要:调用此方法将禁用 SDK,即使在应用程序重启期间也是如此(状态是持久的)。 关闭它的唯一方法是调用 resumeAllTracking 方法。

SwiftObjective-C
Singular.stopAllTracking() 
resumeAllTracking 方法
说明 恢复此用户在此应用程序上的跟踪活动。
签名 (void)resumeAllTracking;
使用示例
SwiftObjective-C
Singular.resumeAllTracking() 
isAllTrackingStopped 方法
描述 检查此用户在此应用程序上的跟踪活动状态。
特征 (BOOL)isAllTrackingStopped;
使用示例
SwiftObjective-C
Singular.isAllTrackingStopped()
// 返回真/假 

6.常见问题

如果您在构建测试应用程序时遇到任何问题或错误,请参阅本节。

为什么我收到"Sandbox: rsync.samba(15813) deny(1) file-read-data..."错误?

在 Xcode 15 中,有一个名为"User Script Sandboxing" 的新选项在构建过程中发挥着至关重要的作用,其目的是防止脚本对系统进行意外更改,从而增强构建的稳定性和安全性。启用该选项后,构建系统会限制用户脚本,禁止未声明的输入/输出依赖关系。这对 Singular SDK 来说是个问题,因为它需要运行脚本来动态链接依赖关系。

要解决这个问题

  1. 导航至Build Settings > Build Options.
  2. "User Script Sandboxing" 调整为"No"
为什么我会收到"No module named Singular" 错误?
  1. 检查是否创建了桥接标头。
  2. 验证桥接标头文件是否链接到Build Settings > Objective-C Bridging Header.
为什么我收到"Arm64" 生成错误?

在某些情况下,iOS 模拟器要求在Build Settings > Excluded Architectures 中排除 arm64。

excluded_architectures_arm64.png

我执行了全局属性。为什么我在测试控制台中看不到它们?

全局属性目前未显示在测试控制台中。 将来会添加全局属性。请使用导出日志验证此功能。

为什么我在 Xcode 控制台中收到以下错误?"SKAdNetwork: Error while updating conversion value:Error Domain=SKANErrorDomain Code=10"?

您可以忽略此错误,因为当 SKAdNetwork 未看到广告系列的印象时,预计会出现此错误(请参阅Apple 对此问题的答复)。

为什么会出现日志错误?

以下常见日志错误可以忽略:

  • [logging] duplicate column name: singular_link in "ALTER TABLE sessions ADD COLUMN singular_link TEXT DEFAULT NULL"
  • [logging] duplicate column name: payload in "ALTER TABLE sessions ADD COLUMN payload TEXT DEFAULT NULL"
  • [logging] duplicate column name: sequence in "ALTER TABLE events ADD COLUMN sequence INTEGER DEFAULT -1"
  • [logging] duplicate column name: payload in "ALTER TABLE events ADD COLUMN payload TEXT DEFAULT NULL"