사전 요구사항
이 연동을 진행하기 전에 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 스킴(예: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이 설치 시 이를 다운로드합니다. AASA 파일은 Singular 플랫폼에서 앱용 링크가 생성될 때 자동으로 생성되고 업데이트됩니다. - Push Notifications — 앱 삭제 트래킹 및 푸시 기반 리인게이지먼트 어트리뷰션에 필수입니다. Push Notifications 지원 및 앱 삭제 트래킹 관련 문서를 참고하세요.
필수 시스템 프레임워크 (수동 연동 시에만 해당 — CocoaPods와 SwiftPM은 자동으로 링크합니다):
-
StoreKit.framework— IAP 및 SKAdNetwork. -
AdServices.framework— Apple Search Ads 어트리뷰션 토큰 (iOS 14.3 이상). -
AppTrackingTransparency.framework— ATT 프롬프트 및 인증 상태 (iOS 14.5 이상). -
UserNotifications.framework— Push Notifications. -
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 -
워크스페이스 열기:
이제부터는
.xcodeproj대신.xcworkspace를 여세요 - 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- 딥링크 스킴
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
구조체 파일
초기화할 진입점:
-
.onOpenURL(of: scenePhase)- 커스텀 URL 스킴 처리 -
.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- 딥링크 스킴
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 print 문 확인
- IDFV를 사용하여 Singular SDK Console 에서 테스트
- 1~2분 이내에 SDK Console에 세션이 표시되는지 확인
선택사항: App Tracking Transparency (ATT)
IDFA 접근에 대한 사용자 권한을 요청하고 어트리뷰션 정확도를 개선하도록 ATT를 구성하세요.
이 섹션을 건너뛸 수 있는 경우: 앱에서 ATT 프롬프트를 표시하지 않는 경우입니다.
ATT 동의를 요청해야 하는 이유
IDFA 이점
iOS 14.5부터 앱은 디바이스의 IDFA (Identifier for Advertisers)에 접근하려면 사용자 권한을 요청해야 합니다.
IDFA가 있을 때와 없을 때의 어트리뷰션:
- IDFA 있음: 정밀한 디바이스 레벨 어트리뷰션과 정확한 설치 매칭
- IDFA 없음: IP, 유저 에이전트 및 디바이스 핑거프린팅을 사용한 확률론적 어트리뷰션
권장사항: 더 나은 어트리뷰션 정확도를 위해 ATT 동의를 요청하세요. Singular는 IDFA 없이도 어트리뷰션이 가능하지만 정확도는 떨어집니다.
ATT 지연 구성
SDK 초기화 지연
첫 세션을 Singular로 전송하기 전에 사용자의 ATT 응답을 기다리도록 타임아웃을 추가하세요.
중요: 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가 어쨌든 데이터를 전송합니다
권장 사례: 어트리뷰션을 위한 IDFA 가용성을 극대화하기 위해 ATT 프롬프트를 가능한 한 빨리(이상적으로는 첫 앱 실행 시) 표시하세요.