iOS SDK - 基本的な統合

ドキュメント

前提条件

Singular SDK を統合する」の手順を完了してください:この統合を進める前に、「計画と前提条件」のステップを完了してください。

重要:これらの前提条件は、Singular SDKを統合する際に必要です。

インストール

お好みのインストール方法を選択してください。ほとんどのプロジェクトにはCocoaPodsをお勧めします。

即決

  • すでにCocoaPodsをお使いですか?方法1を使用
  • SPMのみのプロジェクト?方法2を使用
  • パッケージマネージャを使用しない?方法3を使用

インストール方法

方法1:CocoaPods(推奨)
#

方法1:CocoaPods(推奨)

必要条件

インストールのステップ

  1. Podfileを初期化します(すでにPodfileがある場合はスキップしてください):
    Terminal
    cd /path/to/your/project
    pod init
  2. Singular SDKをPodfileに追加します:
    Podfile
    platform :ios, '12.0'
    
    target 'YourAppName' do
      use_frameworks!
      
      # Singular SDK
      pod 'Singular-SDK'
      
    end
  3. 依存関係をインストールします:
    Terminal
    pod install
  4. ワークスペースを開きます:今後は、.xcodeprojの代わりに.xcworkspace を開いてください。
  5. Swiftプロジェクトのみ: ブリッジングヘッダーを作成する(下記参照
方法2: Swiftパッケージマネージャー
#

方法2: Swiftパッケージマネージャー

インストールステップ

  1. Xcodeでファイル → パッケージの追加
  2. リポジトリのURLを入力:https://github.com/singular-labs/Singular-iOS-SDK
  3. バージョンを選択し、パッケージの追加をクリック
  4. 必要なフレームワークを追加する:
    Build Phases → Link Binary with Librariesに進み、追加する:
    • 必要なライブラリをリンクする:
      Build Phases → Link Binary With Librariesに進み、以下を追加する:
      • Libsqlite3.0.tbd
      • SystemConfiguration.framework
      • Security.フレームワーク
      • Libz.tbd
      • AdSupport.framework
      • WebKit.フレームワーク
      • StoreKit.フレームワーク
      • AdServices.framework (オプションとしてマーク)
  5. Swift プロジェクトのみ: ブリッジング・ヘッダーを作成する(以下を参照
方法3: 手動でフレームワークをインストールする
#

方法3:フレームワークの手動インストール

いつ使うか:CocoaPodsやSPMを使用できない場合にのみ、この方法を使用してください。

フレームワークをダウンロードする:

インストール手順

  1. ダウンロードしたフレームワークを解凍する
  2. Xcodeでプロジェクトを右クリック →[プロジェクト]にファイルを追加
  3. Create Groupsを選択し、フレームワークフォルダを追加する。
  4. 必要なライブラリをリンクする:
    Build Phases → Link Binary With Librariesに進み、追加する:
    • Libsqlite3.0.tbdを追加する。
    • SystemConfiguration.framework
    • Security.フレームワーク
    • Libz.tbd
    • AdSupport.framework
    • WebKit.フレームワーク
    • StoreKit.フレームワーク
    • AdServices.framework (オプションとしてマーク)
  5. フレームワークを埋め込む:
    General → Frameworks, Libraries, and Embedded Contentに移動します。
    SingularフレームワークをEmbed & Signに設定する

Swiftブリッジングヘッダー

重要:CocoaPodsまたはSPMを使用するSwiftプロジェクトに必要。

  1. ヘッダーファイルを作成する:
    Xcode →ファイル → 新規作成 → ファイル → ヘッダーファイル
    名前をYourProjectName-Bridging-Header.hとする。
  2. インポートを追加する
    Bridging Header
    #import <Singular/Singular.h>
  3. ビルド設定でリンクする:
    ビルド設定 → Objective-C ブリッジヘッダ
    に設定する:YourProjectName/YourProjectName-Bridging-Header.h

SDKの設定と初期化

コンフィギュレーション・オブジェクトを作成し、アプリのエントリー・ポイントでSDKを初期化します。


コンフィギュレーション・オブジェクトの作成

基本構成

SDK クレデンシャルとオプション機能でSingularConfig オブジェクトを作成します。この構成は、すべてのアプリアーキテクチャで共通です。

クレデンシャルを取得します:SDKキーとSDKシークレットをSingularプラットフォームのDeveloper Tools → SDK Integrationで見つけてください。

SwiftObjective-C
// 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.
}

SKAdNetworkの自動有効化:SDKバージョン12.0.6から、SKAdNetworkはデフォルトで有効になっています。追加の設定は必要ありません。


SDKの初期化

アプリアーキテクチャの選択

アプリのエントリーポイント毎にSDKを初期化します。初期化のパターンはアプリのアーキテクチャによって異なります。

私はどのアーキテクチャを持っていますか?

  • SceneDelegate:あなたのプロジェクトがSceneDelegate.swift またはSceneDelegate.mを持っているか確認してください。
  • SwiftUI:あなたのアプリは@main struct YourApp: Appで始まります。
  • AppDelegateのみ:iOS13以前のアプリまたはSceneDelegateのないアプリ

モダンiOS:SceneDelegate (iOS 13+)
#

モダンiOS:SceneDelegate (iOS 13+)

コードを追加する場所 SceneDelegate.swift またはSceneDelegate.m

初期化するエントリーポイント

  • willConnectTo session - アプリの起動
  • continue userActivity - ユニバーサルリンク
  • openURLContexts - ディープリンク・スキーム
SwiftObjective-C
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)
    }
}
モダンiOS:SwiftUI アプリ (iOS 14+)
#

モダンiOS:SwiftUI アプリ (iOS 14+)

コードを追加する場所あなたのメインApp構造体ファイル

初期化するエントリーポイント

  • .onOpenURL(of: scenePhase) - カスタムURLスキームを扱う
  • .onContinueUserActivity(of: scenePhase) - ユニバーサルリンク(Singularなディープリンク)を扱う
  • .onChange.active - ディープ・リンクが発生していない場合の初回起動時の初期化を処理します。遅延ディープリンクを処理します。
Swift
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以前)
#

レガシーiOS:AppDelegate (iOS13以前)

コードを追加する場所 AppDelegate.swift またはAppDelegate.m

初期化するエントリポイント

  • didFinishLaunchingWithOptions - アプリの起動
  • continue userActivity - ユニバーサルリンク
  • open url - ディープリンク・スキーム
SwiftObjective-C
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)
    }
}

インストールの確認

飛行前のチェックリスト

統合を構築してテストする前に、以下の項目を確認してください。

  • CocoaPods、SPM、または手動フレームワーク経由でSDKがインストールされていること。
  • Swiftブリッジングヘッダーが作成されている(Swiftを使用している場合)
  • getConfig() 関数を実装
  • Singular.start(config) すべてのエントリポイントで呼び出される
  • SDKキーとSDKシークレットを設定に追加
  • ATTタイムアウトの設定(ATTプロンプトを表示する場合のみ)
  • ディープリンクハンドラを設定(ディープリンクを使用する場合のみ)
  • エラーなしでアプリがビルドされる

次のステップ

  • アプリをビルドして実行する
  • コンソールでIDFVプリントステートメントを確認する
  • IDFV を使用してSingular SDK コンソールでテストします
  • 1~2分以内にSDKコンソールにセッションが表示されることを確認する

オプションアプリ追跡の透明性(ATT)

ATTを設定して、IDFAアクセスに対するユーザー許可を要求し、帰属精度を向上させます。

以下の場合は、このセクションをスキップしてください:アプリにATTプロンプトを表示していない場合。

ATT同意を要求する理由

IDFAの利点

iOS 14.5以降、アプリはデバイスのIDFA(広告主識別子)にアクセスするためのユーザー許可を要求する必要があります。

IDFAを使用する場合と使用しない場合のアトリビューション

  • IDFAあり:正確なデバイスレベルの帰属と正確なインストール照合
  • IDFAなし:IP、ユーザーエージェント、デバイスのフィンガープリントを使用した確率的帰属

推奨:より良い帰属精度のためにATTの同意を求める。IDFAなしでもSingularの帰属は可能だが、精度は低下する。


ATT遅延の設定

遅延SDK初期化

最初のセッションをSingularに送信する前に、ユーザーのATT応答を待つタイムアウトを追加します。

Critical:SDKは最初のセッションを送信する前にATTの同意を待たなければなりません。そうしないと、最初のアトリビューションイベントにIDFAが含まれません。

SwiftObjective-C
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
}

ATTフローのタイムライン

ATTディレイを設定すると、以下のようになる:

  1. アプリの起動:SDKがイベントの記録を開始するが、まだイベントは送信されない。
  2. ATTプロンプト:アプリがATT同意ダイアログを表示
  3. ユーザー応答:ユーザーが許可または拒否
  4. SDKはデータを送信します:SDKは直ちにIDFAでキューに入ったイベントを送信します(許可された場合)。
  5. タイムアウト・フォールバック:応答なしで300秒経過した場合、SDKはデータを送信します。

ベストプラクティスアトリビューションのためにIDFAを最大限に活用するために、できるだけ早い段階で(理想的には最初のアプリ起動時に)ATTプロンプトを表示しましょう。