모바일 SDK인앱 이벤트
iOS
AdStage 인앱 이벤트 통합 가이드 (iOS)
목차
개요
AdStage 인앱 이벤트는 사용자 행동과 앱 이벤트를 추적하여 마케팅 분석 및 최적화를 지원합니다.
주요 기능
- 전역 컨텍스트 관리: 사용자/디바이스 정보를 한 번 설정하면 자동 포함
- 간단한 API: 이벤트 이름과 파라미터만으로 간편하게 전송
- 자동 세션 추적: 세션 ID 자동 생성 및 관리
- 비동기 처리: 네트워크 통신이 UI를 블록하지 않음
- 오프라인 지원: 네트워크 복구 시 자동 재전송
기본 설정
1. SDK 초기화 (필수)
import AdapterAdStage
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// AdStage 초기화
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app"
)
// 전역 설정
setupGlobalContext()
return true
}
private func setupGlobalContext() {
// 디바이스 정보 설정
setupDeviceInfo()
// 사용자가 로그인한 경우
if let userId = UserDefaults.standard.string(forKey: "userId") {
setupUserContext(userId: userId)
}
}
}2. 전역 디바이스 정보 설정
import UIKit
import AdapterAdStage
func setupDeviceInfo() {
let device = UIDevice.current
let app = Bundle.main
AdStageSDK.shared.setDeviceProperties(
category: device.userInterfaceIdiom == .pad ? "tablet" : "mobile",
platform: "iOS",
model: deviceModelName(),
appVersion: app.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown",
osVersion: device.systemVersion
)
print("✅ 디바이스 정보 설정 완료")
}
// 정확한 디바이스 모델명 가져오기
func deviceModelName() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}3. 사용자 정보 설정
func setupUserContext(userId: String) {
// 사용자 ID 설정
AdStageSDK.shared.setUserId(userId)
// 사용자 속성 설정
if let userProfile = loadUserProfile() {
AdStageSDK.shared.setUserProperties(
gender: userProfile.gender,
country: userProfile.country,
city: userProfile.city,
age: "\(userProfile.age)",
language: Locale.current.languageCode ?? "en"
)
}
print("✅ 사용자 정보 설정 완료: \(userId)")
}
func clearUserContext() {
// 로그아웃 시 호출
AdStageSDK.shared.clearEventContext()
print("🚪 사용자 정보 초기화")
}기본 이벤트 전송
1. 가장 간단한 방식
import AdapterAdStage
// 이벤트 이름만 전송
AdStageSDK.shared.track("APP_OPEN")2. 파라미터 포함
// 파라미터와 함께 전송
AdStageSDK.shared.track("BUTTON_CLICK", params: [
"button_id": "promo_banner",
"screen": "home",
"action": "tap"
])3. 다양한 데이터 타입 지원
AdStageSDK.shared.track("PURCHASE", params: [
"transaction_id": "TX_123456",
"value": 99000, // Int
"tax": 9900.0, // Double
"currency": "KRW", // String
"is_first_purchase": true, // Bool
"items": ["item1", "item2"], // Array
"metadata": [ // Dictionary
"payment_method": "card",
"shipping": "express"
]
])4. 현재 설정 확인
// 현재 사용자 ID
if let userId = AdStageSDK.shared.getUserId() {
print("Current User: \(userId)")
}
// 현재 세션 ID
if let sessionId = AdStageSDK.shared.getSessionId() {
print("Current Session: \(sessionId)")
}
// 전체 상태 출력 (디버깅용)
AdStageSDK.shared.printEventStatus()사용자 및 디바이스 정보
1. 사용자 ID 관리
class UserManager {
// 로그인
func onUserLogin(userId: String) {
AdStageSDK.shared.setUserId(userId)
// 로그인 이벤트
AdStageSDK.shared.track("USER_LOGIN", params: [
"login_method": "email"
])
print("✅ 로그인: \(userId)")
}
// 로그아웃
func onUserLogout() {
// 로그아웃 이벤트 먼저 전송
AdStageSDK.shared.track("USER_LOGOUT")
// 사용자 정보 초기화
AdStageSDK.shared.clearEventContext()
print("🚪 로그아웃 완료")
}
// 회원가입
func onUserSignup(userId: String, method: String) {
AdStageSDK.shared.setUserId(userId)
AdStageSDK.shared.track("USER_SIGNUP", params: [
"signup_method": method,
"timestamp": Date().timeIntervalSince1970
])
}
}2. 사용자 속성 업데이트
class UserProfileManager {
func updateUserProfile(_ profile: UserProfile) {
AdStageSDK.shared.setUserProperties(
gender: profile.gender,
country: profile.country,
city: profile.city,
age: "\(profile.age)",
language: profile.language
)
// 프로필 업데이트 이벤트
AdStageSDK.shared.track("PROFILE_UPDATED")
print("✅ 프로필 업데이트 완료")
}
func updateLocation(country: String, city: String) {
AdStageSDK.shared.setUserProperties(
country: country,
city: city
)
print("📍 위치 업데이트: \(city), \(country)")
}
}3. 디바이스 정보 업데이트
class DeviceInfoManager {
func updateAppVersion() {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
AdStageSDK.shared.setDeviceProperties(
appVersion: appVersion
)
print("📱 앱 버전 업데이트: \(appVersion ?? "unknown")")
}
func updateOSVersion() {
let osVersion = UIDevice.current.systemVersion
AdStageSDK.shared.setDeviceProperties(
osVersion: osVersion
)
print("💿 OS 버전 업데이트: iOS \(osVersion)")
}
}4. 세션 관리
class SessionManager {
private var sessionStartTime: Date?
// 세션 시작
func startSession() {
sessionStartTime = Date()
AdStageSDK.shared.track("SESSION_START", params: [
"timestamp": sessionStartTime?.timeIntervalSince1970 ?? 0
])
print("▶️ 세션 시작")
}
// 세션 종료
func endSession() {
guard let startTime = sessionStartTime else { return }
let duration = Date().timeIntervalSince(startTime)
AdStageSDK.shared.track("SESSION_END", params: [
"duration_seconds": Int(duration),
"timestamp": Date().timeIntervalSince1970
])
print("⏹️ 세션 종료: \(Int(duration))초")
}
}이벤트 타입별 예제
1. 앱 라이프사이클 이벤트
import UIKit
import AdapterAdStage
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// SDK 초기화
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app"
)
// 앱 시작 이벤트
trackAppLaunch(launchOptions: launchOptions)
return true
}
private func trackAppLaunch(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
var params: [String: Any] = [
"version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "unknown",
"build": Bundle.main.infoDictionary?["CFBundleVersion"] ?? "unknown"
]
// 푸시 알림으로 실행된 경우
if let notification = launchOptions?[.remoteNotification] {
params["launch_source"] = "push_notification"
}
// URL로 실행된 경우
if let url = launchOptions?[.url] {
params["launch_source"] = "deep_link"
params["url"] = "\(url)"
}
AdStageSDK.shared.track("APP_LAUNCH", params: params)
}
func applicationDidEnterBackground(_ application: UIApplication) {
AdStageSDK.shared.track("APP_BACKGROUND")
}
func applicationWillEnterForeground(_ application: UIApplication) {
AdStageSDK.shared.track("APP_FOREGROUND")
}
func applicationWillTerminate(_ application: UIApplication) {
AdStageSDK.shared.track("APP_TERMINATE")
}
}2. 화면 조회 이벤트
import UIKit
import AdapterAdStage
class BaseViewController: UIViewController {
// 화면 이름 (하위 클래스에서 오버라이드)
var screenName: String {
return String(describing: type(of: self))
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
trackScreenView()
}
private func trackScreenView() {
AdStageSDK.shared.track("SCREEN_VIEW", params: [
"screen_name": screenName,
"screen_class": String(describing: type(of: self)),
"timestamp": Date().timeIntervalSince1970
])
print("📺 Screen View: \(screenName)")
}
}
// 사용 예제
class HomeViewController: BaseViewController {
override var screenName: String { "home" }
}
class ProductDetailViewController: BaseViewController {
var productId: String?
override var screenName: String { "product_detail" }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let productId = productId {
AdStageSDK.shared.track("VIEW_ITEM", params: [
"item_id": productId
])
}
}
}3. 전자상거래 이벤트
import AdapterAdStage
class EcommerceTracker {
// 상품 조회
func trackProductView(product: Product) {
AdStageSDK.shared.track("VIEW_ITEM", params: [
"item_id": product.id,
"item_name": product.name,
"item_category": product.category,
"price": product.price,
"currency": "KRW"
])
}
// 장바구니 추가
func trackAddToCart(product: Product, quantity: Int) {
AdStageSDK.shared.track("ADD_TO_CART", params: [
"item_id": product.id,
"item_name": product.name,
"quantity": quantity,
"price": product.price,
"value": product.price * quantity,
"currency": "KRW"
])
}
// 장바구니 제거
func trackRemoveFromCart(product: Product, quantity: Int) {
AdStageSDK.shared.track("REMOVE_FROM_CART", params: [
"item_id": product.id,
"quantity": quantity,
"value": product.price * quantity
])
}
// 구매 시작
func trackBeginCheckout(cart: ShoppingCart) {
AdStageSDK.shared.track("BEGIN_CHECKOUT", params: [
"value": cart.total,
"currency": "KRW",
"item_count": cart.items.count,
"coupon": cart.couponCode ?? ""
])
}
// 결제 정보 입력
func trackAddPaymentInfo(paymentMethod: String) {
AdStageSDK.shared.track("ADD_PAYMENT_INFO", params: [
"payment_method": paymentMethod
])
}
// 구매 완료
func trackPurchase(order: Order) {
AdStageSDK.shared.track("PURCHASE", params: [
"transaction_id": order.id,
"value": order.total,
"currency": "KRW",
"tax": order.tax,
"shipping": order.shipping,
"coupon": order.couponCode ?? "",
"payment_method": order.paymentMethod,
"item_count": order.items.count,
"items": order.items.map { [
"id": $0.id,
"name": $0.name,
"price": $0.price,
"quantity": $0.quantity
]}
])
print("✅ 구매 완료: \(order.id)")
}
// 환불
func trackRefund(orderId: String, refundAmount: Int, reason: String) {
AdStageSDK.shared.track("REFUND", params: [
"transaction_id": orderId,
"value": refundAmount,
"currency": "KRW",
"reason": reason
])
}
}4. 사용자 행동 이벤트
class UserActionTracker {
// 검색
func trackSearch(query: String, filters: [String: Any]? = nil) {
var params: [String: Any] = [
"search_term": query
]
if let filters = filters {
params["filters"] = filters
}
AdStageSDK.shared.track("SEARCH", params: params)
}
// 검색 결과 클릭
func trackSearchResultClick(query: String, resultIndex: Int, itemId: String) {
AdStageSDK.shared.track("SEARCH_RESULT_CLICK", params: [
"search_term": query,
"position": resultIndex,
"item_id": itemId
])
}
// 공유
func trackShare(contentType: String, contentId: String, method: String) {
AdStageSDK.shared.track("SHARE", params: [
"content_type": contentType,
"content_id": contentId,
"method": method // "messages", "kakao", "instagram"
])
}
// 좋아요
func trackLike(contentType: String, contentId: String) {
AdStageSDK.shared.track("LIKE", params: [
"content_type": contentType,
"content_id": contentId
])
}
// 북마크
func trackBookmark(contentType: String, contentId: String, action: String) {
AdStageSDK.shared.track("BOOKMARK", params: [
"content_type": contentType,
"content_id": contentId,
"action": action // "add", "remove"
])
}
// 댓글 작성
func trackComment(contentType: String, contentId: String, commentLength: Int) {
AdStageSDK.shared.track("COMMENT", params: [
"content_type": contentType,
"content_id": contentId,
"length": commentLength
])
}
// 리뷰 작성
func trackReview(productId: String, rating: Int, hasText: Bool, hasPhoto: Bool) {
AdStageSDK.shared.track("WRITE_REVIEW", params: [
"product_id": productId,
"rating": rating,
"has_text": hasText,
"has_photo": hasPhoto
])
}
}5. 게임 이벤트
class GameEventTracker {
// 레벨 시작
func trackLevelStart(level: Int, levelName: String) {
AdStageSDK.shared.track("LEVEL_START", params: [
"level_number": level,
"level_name": levelName
])
}
// 레벨 완료
func trackLevelComplete(level: Int, score: Int, duration: TimeInterval, stars: Int) {
AdStageSDK.shared.track("LEVEL_COMPLETE", params: [
"level_number": level,
"score": score,
"duration_seconds": Int(duration),
"stars": stars,
"success": true
])
}
// 레벨 실패
func trackLevelFail(level: Int, reason: String, attemptCount: Int) {
AdStageSDK.shared.track("LEVEL_FAIL", params: [
"level_number": level,
"fail_reason": reason,
"attempt_count": attemptCount,
"success": false
])
}
// 가상 화폐 획득
func trackEarnVirtualCurrency(currencyName: String, amount: Int, source: String) {
AdStageSDK.shared.track("EARN_VIRTUAL_CURRENCY", params: [
"virtual_currency_name": currencyName,
"value": amount,
"source": source // "level_reward", "daily_bonus", "purchase"
])
}
// 가상 화폐 사용
func trackSpendVirtualCurrency(currencyName: String, amount: Int, itemName: String, itemCategory: String) {
AdStageSDK.shared.track("SPEND_VIRTUAL_CURRENCY", params: [
"virtual_currency_name": currencyName,
"value": amount,
"item_name": itemName,
"item_category": itemCategory
])
}
// 아이템 획득
func trackUnlockAchievement(achievementId: String, achievementName: String) {
AdStageSDK.shared.track("UNLOCK_ACHIEVEMENT", params: [
"achievement_id": achievementId,
"achievement_name": achievementName
])
}
// 튜토리얼 시작
func trackTutorialBegin(tutorialId: String) {
AdStageSDK.shared.track("TUTORIAL_BEGIN", params: [
"tutorial_id": tutorialId
])
}
// 튜토리얼 완료
func trackTutorialComplete(tutorialId: String, duration: TimeInterval) {
AdStageSDK.shared.track("TUTORIAL_COMPLETE", params: [
"tutorial_id": tutorialId,
"duration_seconds": Int(duration)
])
}
// 게임 시작
func trackGameStart(gameMode: String, difficulty: String) {
AdStageSDK.shared.track("GAME_START", params: [
"game_mode": gameMode,
"difficulty": difficulty
])
}
// 게임 종료
func trackGameEnd(gameMode: String, score: Int, duration: TimeInterval, result: String) {
AdStageSDK.shared.track("GAME_END", params: [
"game_mode": gameMode,
"score": score,
"duration_seconds": Int(duration),
"result": result // "win", "lose", "draw"
])
}
}6. 미디어 이벤트
class MediaTracker {
// 비디오 시작
func trackVideoStart(videoId: String, videoTitle: String, duration: TimeInterval) {
AdStageSDK.shared.track("VIDEO_START", params: [
"video_id": videoId,
"video_title": videoTitle,
"duration": Int(duration)
])
}
// 비디오 완료
func trackVideoComplete(videoId: String, watchedDuration: TimeInterval, totalDuration: TimeInterval) {
let completionRate = (watchedDuration / totalDuration) * 100
AdStageSDK.shared.track("VIDEO_COMPLETE", params: [
"video_id": videoId,
"watched_seconds": Int(watchedDuration),
"total_seconds": Int(totalDuration),
"completion_rate": Int(completionRate)
])
}
// 오디오 재생
func trackAudioPlay(audioId: String, audioTitle: String, artist: String) {
AdStageSDK.shared.track("AUDIO_PLAY", params: [
"audio_id": audioId,
"audio_title": audioTitle,
"artist": artist
])
}
// 플레이리스트 생성
func trackPlaylistCreate(playlistId: String, playlistName: String, itemCount: Int) {
AdStageSDK.shared.track("PLAYLIST_CREATE", params: [
"playlist_id": playlistId,
"playlist_name": playlistName,
"item_count": itemCount
])
}
}7. 광고 이벤트
class AdEventTracker {
// 광고 노출
func trackAdImpression(adType: String, adId: String, adPlacement: String) {
AdStageSDK.shared.track("AD_IMPRESSION", params: [
"ad_type": adType, // "banner", "interstitial", "rewarded", "native"
"ad_id": adId,
"ad_placement": adPlacement
])
}
// 광고 클릭
func trackAdClick(adType: String, adId: String, adPlacement: String) {
AdStageSDK.shared.track("AD_CLICK", params: [
"ad_type": adType,
"ad_id": adId,
"ad_placement": adPlacement
])
}
// 리워드 광고 시작
func trackAdRewardStart(adId: String, rewardType: String) {
AdStageSDK.shared.track("AD_REWARD_START", params: [
"ad_id": adId,
"reward_type": rewardType
])
}
// 리워드 광고 완료
func trackAdRewardEarned(adId: String, rewardType: String, rewardAmount: Int) {
AdStageSDK.shared.track("AD_REWARD_EARNED", params: [
"ad_id": adId,
"reward_type": rewardType,
"reward_amount": rewardAmount
])
}
}베스트 프랙티스
1. 이벤트 매니저 Singleton
import AdapterAdStage
class AnalyticsManager {
static let shared = AnalyticsManager()
private init() {}
func trackEvent(_ eventName: String, params: [String: Any]? = nil) {
var finalParams = params ?? [:]
// 공통 파라미터 자동 추가
finalParams["timestamp"] = Date().timeIntervalSince1970
finalParams["app_version"] = Bundle.main.infoDictionary?["CFBundleShortVersionString"]
AdStageSDK.shared.track(eventName, params: finalParams)
#if DEBUG
print("📊 Event: \(eventName)")
if let params = finalParams as? [String: String] {
params.forEach { print(" - \($0.key): \($0.value)") }
}
#endif
}
}
// 사용
AnalyticsManager.shared.trackEvent("BUTTON_CLICK", params: [
"button_id": "purchase"
])2. Extension으로 간편화
import UIKit
import AdapterAdStage
extension UIViewController {
func trackScreen() {
let screenName = String(describing: type(of: self))
AdStageSDK.shared.track("SCREEN_VIEW", params: [
"screen_name": screenName
])
}
}
// 사용
class ProfileViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
trackScreen()
}
}3. 네이밍 규칙
struct EventNames {
// 앱 라이프사이클
static let appLaunch = "APP_LAUNCH"
static let appForeground = "APP_FOREGROUND"
static let appBackground = "APP_BACKGROUND"
// 사용자 행동
static let userLogin = "USER_LOGIN"
static let userSignup = "USER_SIGNUP"
static let userLogout = "USER_LOGOUT"
// 전자상거래
static let viewItem = "VIEW_ITEM"
static let addToCart = "ADD_TO_CART"
static let purchase = "PURCHASE"
// 게임
static let levelStart = "LEVEL_START"
static let levelComplete = "LEVEL_COMPLETE"
}
struct EventParams {
static let screenName = "screen_name"
static let itemId = "item_id"
static let value = "value"
static let currency = "currency"
}
// 사용
AdStageSDK.shared.track(EventNames.purchase, params: [
EventParams.value: 10000,
EventParams.currency: "KRW"
])4. Throttling (과도한 호출 방지)
class ThrottledTracker {
private var lastTrackTime: [String: Date] = [:]
private let throttleInterval: TimeInterval = 1.0 // 1초
func trackEvent(_ eventName: String, params: [String: Any]? = nil) {
let now = Date()
if let lastTime = lastTrackTime[eventName],
now.timeIntervalSince(lastTime) < throttleInterval {
// 너무 짧은 시간 내에 같은 이벤트 호출, 무시
return
}
lastTrackTime[eventName] = now
AdStageSDK.shared.track(eventName, params: params)
}
}
// 사용 (스크롤 이벤트 등)
let throttledTracker = ThrottledTracker()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
throttledTracker.trackEvent("SCROLL", params: [
"offset_y": scrollView.contentOffset.y
])
}5. 에러 추적
extension AnalyticsManager {
func trackError(_ error: Error, context: String) {
AdStageSDK.shared.track("ERROR_OCCURRED", params: [
"error_message": error.localizedDescription,
"error_domain": (error as NSError).domain,
"error_code": (error as NSError).code,
"context": context,
"timestamp": Date().timeIntervalSince1970
])
}
}
// 사용
do {
try someOperation()
} catch {
AnalyticsManager.shared.trackError(error, context: "payment_processing")
}트러블슈팅
1. 이벤트가 전송되지 않음
// 초기화 확인
func checkInitialization() {
// SDK 초기화 여부 확인
let userId = AdStageSDK.shared.getUserId()
print("User ID: \(userId ?? "nil")")
let sessionId = AdStageSDK.shared.getSessionId()
print("Session ID: \(sessionId ?? "nil")")
// 전체 상태 출력
AdStageSDK.shared.printEventStatus()
}2. 네트워크 문제
import Network
class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "NetworkMonitor")
var isConnected: Bool = true
private init() {
monitor.pathUpdateHandler = { [weak self] path in
self?.isConnected = path.status == .satisfied
if path.status == .satisfied {
print("✅ 네트워크 연결됨")
} else {
print("❌ 네트워크 연결 끊김")
}
}
monitor.start(queue: queue)
}
}
// 사용
if NetworkMonitor.shared.isConnected {
AdStageSDK.shared.track("EVENT_NAME")
} else {
print("⚠️ 네트워크 연결 없음, 이벤트는 나중에 전송됨")
// SDK가 자동으로 재시도함
}3. 디버그 로깅
#if DEBUG
extension AdStageSDK {
func trackDebug(_ eventName: String, params: [String: Any]? = nil) {
print("""
==========================================
📊 Event Track
==========================================
Event: \(eventName)
User ID: \(getUserId() ?? "nil")
Session ID: \(getSessionId() ?? "nil")
Params:
\(params?.map { " \($0.key): \($0.value)" }.joined(separator: "\n") ?? " none")
==========================================
""")
track(eventName, params: params)
}
}
#endif
// 사용
#if DEBUG
AdStageSDK.shared.trackDebug("TEST_EVENT", params: ["test": true])
#else
AdStageSDK.shared.track("TEST_EVENT", params: ["test": true])
#endif4. 메모리 누수 방지
class EventTrackerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ❌ BAD: Strong reference cycle
// NotificationCenter.default.addObserver(self, ...)
// ✅ GOOD: Weak self
NotificationCenter.default.addObserver(
forName: .dataUpdated,
object: nil,
queue: .main
) { [weak self] notification in
self?.handleDataUpdate(notification)
}
}
deinit {
NotificationCenter.default.removeObserver(self)
print("✅ ViewController deinitialized")
}
}
