SKAdNetwork 전환 값 매핑: 동적 SKAN 스키마 자동화

opoinstall
2026-08-25
5 min read

MMP는 어떻게 SKAdNetwork 스키마를 자동으로 매핑할까요? MMP(모바일 어트리뷰션 파트너) 또는 어트리뷰션 백엔드는 인앱 이벤트와 수익 단계를 중앙 집중형 콘솔의 동적 버전 관리 JSON 설정 스키마로 변환하여 SKAdNetwork 스키마 매핑을 자동화합니다. 모바일 SDK는 실행 시 이 설정을 가져와 런타임에 로컬에서 전환 규칙을 평가하므로, 지원되는 전환 규칙 변경 사항이 새로운 앱 바이너리 배포 없이 적용될 수 있습니다.

SKAdNetwork 전환 값 스키마는 수익 거래, 온보딩 마일스톤, 기능 참여 등 인앱 사용자 행동을 Apple의 6비트 세밀한 값(0~63) 및 3단계 개략적인 값(low, medium, high)에 매핑하는 벤더 또는 애플리케이션 수준의 규칙 집합입니다. 동적 매핑 아키텍처는 버전 관리된 설정 파일을 클라우드 백엔드에서 클라이언트 SDK로 배포하므로, 컴파일된 iOS 애플리케이션 바이너리에 전환 로직을 하드코딩할 필요가 없습니다.

용어 정의
SKAdNetwork 개인정보 보호를 준수하는 광고 캠페인 어트리뷰션을 위한 Apple의 플랫폼 수준 프레임워크입니다.
전환 값 스키마 인앱 이벤트 마일스톤을 세밀한 값과 개략적인 값에 매핑하는 벤더 또는 앱 정의 설정입니다.
동적 스키마 매핑 SDK를 통한 전환 규칙의 자동 배포 및 런타임 평가입니다.
윈도우 잠금 활성 전환 창을 조기에 마감하는 API 매개변수(lockWindow: true)입니다.

자동화된 SKAdNetwork 전환 값 매핑의 아키텍처

Apple 플랫폼 레이어와 벤더 스키마 레이어의 구분

안정적인 전환 값 엔진을 설계하려면 엔지니어링 팀이 Apple의 기본 프레임워크 규칙과 벤더 수준의 스키마 추상화를 분리해야 합니다:

  • Apple 플랫폼 레이어: 최초 실행 후 세 가지 순차적 전환 창(Day 0~2, Day 3~7, Day 8~35), 6비트 세밀한 값(0~63), 개략적인 값(low, medium, high), 포스트백 데이터 계층, 그리고 SKAdNetwork.updatePostbackConversionValue API를 포함한 핵심 운영체제 프리미티브를 관리합니다.
  • 벤더 스키마 레이어: 수익 버킷팅, 온보딩 퍼널 진행 상황, 비트 단위 플래그 할당, 원격 JSON 동기화, 클라이언트 측 규칙 평가 등 애플리케이션이 정의한 비즈니스 규칙을 포괄합니다.
┌────────────────────────────────────────┐
│                           Vendor Schema Layer                                  │
│  [MMP / Analytics Console] ──► [Publishes Versioned JSON Configuration]     │
│                                              │                                │
│  [Client Mobile SDK]       ──► [Evaluates In-App Events Locally in Memory]  │
└──────────────────────────────────────┬─┘
                                       │ (Calculates Fine, Coarse, & Lock)
                                       ▼
┌────────────────────────────────────────┐
│                           Apple Platform Layer                                 │
│  [StoreKit Framework]      ──► [SKAdNetwork.updatePostbackConversionValue]  │
│  [Operating System]        ──► [Manages Conversion Windows & Timers]        │
│  [System]                  ──► [Prepares and Sends Signed Postback]         │
└────────────────────────────────────────┘

MMP 콘솔에서 StoreKit으로의 동적 SKAN 스키마 매핑

하드코딩된 전환 로직의 문제점

iOS 애플리케이션 타겟 내부에 전환 로직을 직접 하드코딩하면 상당한 운영상의 제약이 발생합니다:

  • 앱 스토어 검토 종속성: 수익 임계값, 이벤트 가중치 또는 창 잠금 트리거를 수정하려면 전체 바이너리 릴리스 주기가 필요합니다.
  • 버전 단편화: 프로덕션 환경의 여러 과거 앱 버전이 상충하는 전환 의미를 전송하여 다운스트림 보고 모델을 손상시킵니다.
  • 최적화 유연성 부족: 성장 팀이 실시간 마케팅 성과에 대응하여 참여 중심 캠페인과 수익화 중심 캠페인 사이에서 전환 전략을 조정할 수 없습니다.

동적 설정 전달 파이프라인

자동화된 매핑 아키텍처는 다단계 파이프라인을 통해 컴파일된 바이너리에서 전환 로직을 분리합니다:

  1. 콘솔 설정: 마케터와 분석가는 중앙 집중형 대시보드에서 이벤트 가중치, 통화 계층, 창 잠금 규칙을 설정합니다.
  2. 스키마 버전 관리 및 고정: 백엔드는 버전 관리된 JSON 설정 페이로드를 게시합니다. 사용자의 35일 전환 수명 주기 동안 의미론적 드리프트를 방지하기 위해, 안정적인 벤더 구현은 초기 전환 창 동안 설정된 활성 스키마 구성을 고정하여 앱을 재시작해도 정확한 매핑 규칙이 유지되도록 합니다.
  3. 클라이언트 수집 및 캐싱: 모바일 SDK는 앱 초기화 시 활성 스키마를 다운로드하고 로컬 영구 저장소에 설정 페이로드와 버전 메타데이터를 모두 캐시합니다.
  4. 로컬 규칙 평가: 인앱 이벤트가 발생하면 SDK는 이벤트 실행 경로에 동기식 원격 설정 요청을 추가하지 않고, 캐시된 규칙 집합과 로컬에서 비교하여 평가합니다.

하드코딩된 SKAN 로직과 동적 스키마 설정 비교

관련 항목: SKAdNetwork ──> 모바일 어트리뷰션 아키텍처

SKAN 4.0 창 전반에 걸친 동적 전환 값 스키마 설계

다중 창 스키마 파티셔닝

SKAdNetwork 4.0은 앱의 최초 실행을 기준으로 하는 세 가지 순차적 창에 걸쳐 전환 측정을 구조화합니다:

  • 창 1 (Day 0~2): 최초 실행 후 첫 48시간.
  • 창 2 (Day 3~7): 최초 실행 후 48시간부터 168시간까지.
  • 창 3 (Day 8~35): 최초 실행 후 168시간부터 840시간까지.

동적 스키마 엔진은 이러한 창에 걸쳐 규칙을 분할하여 초기 애플리케이션 실행 이후 경과된 시간을 기반으로 적절한 값 계산을 실행합니다.

창 1 (Day 0~2): 세밀한 값과 개략적인 값 구조화

창 1은 세밀한 전환 값을 공개할 수 있는 유일한 전환 창입니다. 창 1에 대한 설정은 두 가지 동시 매핑을 정의합니다:

  • 세밀한 매핑 (0~63): 초기 수익화 계층, 온보딩 마일스톤 또는 복합 참여 점수를 포착하는 고해상도 규칙입니다.
  • 개략적인 매핑 (low, medium, high): 할당된 포스트백 데이터 계층이 세밀한 보고를 허용하지 않을 때 공개되는 저해상도 폴백 상태입니다.

창 2 (Day 3~7) 및 창 3 (Day 8~35): 개략적인 수명 주기 추적

두 번째 및 세 번째 포스트백은 세밀한 전환 값을 노출하지 않으며, 적격 데이터 계층의 경우 개략적인 값만 노출합니다.

창 2 및 창 3의 스키마는 장기 유지 및 수익화 마일스톤에 중점을 둡니다:

  • 창 2 개략적 매핑: 퍼널 중간 단계 유지율 평가(예: low = Day 3~7 활성; medium = 세션 3회 완료; high = 재구매 또는 체험판 전환).
  • 창 3 개략적 매핑: 장기 유지율 및 구독 갱신 평가(예: low = Day 8~35 유지; medium = 레벨 마일스톤 달성; high = 활성 유료 구독자).

전환 스키마를 설정하는 개발자는 다중 창 규칙 구조에 대한 기술 지침을 위해 SKAN 전환 매핑 문서를 참조할 수 있습니다.

SKAN 4 전환 창 세밀한 값 및 개략적인 값 매핑


벤더 정의 인코딩 모델: 수익 버킷팅, 퍼널 및 비트 단위 로직

이러한 인코딩 모델은 Apple이 규정한 스키마 유형이라기보다는 벤더 및 애플리케이션 수준의 디자인 패턴을 나타냅니다.

수익 기반 스키마

수익 스키마는 누적 구매 금액에 걸쳐 사용 가능한 세밀한 값을 할당합니다:

  • 선형 버킷팅: 수익 범위를 동일한 간격으로 나눕니다(예: 최대 $96.00까지 $1.50 단위의 버킷 64개). 예측 가능한 거래 규모를 가진 애플리케이션에 이상적입니다.
  • 로그 버킷팅: 저비용 구매에는 세밀한 버킷을 할당하고 고가 거래에는 버킷 범위를 확장합니다(예: 값 1~20은 $0.99~$19.99 커버; 값 21~50은 $20.00~$100.00 커버; 값 51~63은 $100.00~$1000.00+ 커버).
  • 백분위수 기반 버킷팅: 경험적 수익화 곡선을 기반으로 과거 사용자 구매 분포를 코호트 세그먼트에 매핑합니다.

퍼널 진행 스키마 및 값 방향성

SKAdNetwork 3 및 이전 버전에서 Apple은 전환 값이 단조 증가하도록 요구했습니다. SKAdNetwork 4.0에서 Apple은 이 제약 조건을 제거하여 창 1의 전환 값이 후속 API 호출에 걸쳐 증가하거나 감소할 수 있도록 허용했습니다.

그러나 많은 어트리뷰션 스키마는 더 높은 값이 점진적으로 더 강력한 상업적 결과를 나타내도록 보장하기 위해 벤더 수준의 설계 규칙으로 의도적으로 단조 증가를 적용합니다:

  • 0: 앱 설치 및 열기.
  • 10: 등록 완료.
  • 20: 온보딩 튜토리얼 완료.
  • 30: 결제 수단 추가.
  • 45: 장바구니에 상품 추가.
  • 63: 초기 결제 완료.

비트 단위 범주형 스키마

비트 단위 스키마는 6비트 정수(26=642^6 = 64)를 6개의 독립적인 부울 플래그(b5b4b3b2b1b0b_5 b_4 b_3 b_2 b_1 b_0)으로 취급합니다:

비트 위치 이진 가중치 매핑된 인앱 행동
비트 0 (b0b_0) 1 (0b000001) 사용자 등록 완료
비트 1 (b1b_1) 2 (0b000010) 사용자 푸시 알림 허용
비트 2 (b2b_2) 4 (0b000100) 사용자가 위시리스트에 상품 추가
비트 3 (b3b_3) 8 (0b001000) 사용자 추천 링크 공유
비트 4 (b4b_4) 16 (0b010000) 사용자 인앱 구매 완료
비트 5 (b5b_5) 32 (0b100000) 사용자 프리미엄 체험판 구독

아래의 버전 관리된 JSON 페이로드는 다중 창 동적 설정 문서를 보여줍니다:

{
  "schema_version": "4.0.1",
  "app_id": "1234567890",
  "currency": "USD",
  "windows": {
    "window_1": {
      "mode": "hybrid_revenue_and_funnel",
      "fine_mapping": [
        { "event": "app_open", "min_revenue_cents": 0, "fine_value": 0, "lock": false },
        { "event": "registration_complete", "min_revenue_cents": 0, "fine_value": 10, "lock": false },
        { "event": "tutorial_complete", "min_revenue_cents": 0, "fine_value": 20, "lock": false },
        { "event": "purchase", "min_revenue_cents": 99, "fine_value": 30, "lock": false },
        { "event": "purchase", "min_revenue_cents": 999, "fine_value": 45, "lock": false },
        { "event": "purchase", "min_revenue_cents": 4999, "fine_value": 63, "lock": true }
      ],
      "coarse_mapping": {
        "low": { "events": ["app_open", "registration_complete"] },
        "medium": { "events": ["tutorial_complete"] },
        "high": { "events": ["purchase"] }
      }
    },
    "window_2": {
      "mode": "coarse_retention_and_monetization",
      "coarse_mapping": {
        "low": { "events": ["app_open"], "lock": false },
        "medium": { "events": ["session_milestone"], "lock": false },
        "high": { "events": ["repeat_purchase"], "lock": true }
      }
    },
    "window_3": {
      "mode": "coarse_long_tail_ltv",
      "coarse_mapping": {
        "low": { "events": ["app_open"], "lock": false },
        "medium": { "events": ["level_milestone"], "lock": false },
        "high": { "events": ["subscription_active"], "lock": true }
      }
    }
  }
}

동적 SDK 설정: 런타임에 원격 설정 수집 및 평가

클라이언트 측 규칙 평가 메커니즘

어트리뷰션 SDK는 애플리케이션 런타임 내에서 로컬로 전환 규칙을 평가합니다:

    이벤트 경로에서의 동기식 원격 설정 가져오기 없음: 인앱 작업은 활성 규칙 집합에 대해 메모리 내 로컬 평가를 트리거하며, 애플리케이션 실행을 차단하지 않고 즉시 StoreKit API를 호출합니다.
  • 데이터 최소화: 여기에 표시된 SKAdNetwork 전환 업데이트 경로의 경우, 원시 이벤트 입력을 로컬에서 평가할 수 있으며 결과 전환 값만 StoreKit에 전달하면 됩니다. 이는 SDK에 의해 구현된 다른 분석 데이터 흐름을 자체적으로 설명하거나 제한하지 않습니다.

오프라인 상태 및 로컬 지속성 처리

애플리케이션이 오프라인 상태이거나 네트워크 상태가 저하된 상태로 실행되는 경우:

  1. SDK는 로컬 지속성에서 최초 실행 타임스탬프 앵커를 독립적으로 초기화합니다.
  2. SDK는 영구 로컬 저장소에서 고정된 설정 스키마를 로드하여 캐시된 페이로드가 고정된 스키마 버전과 일치하는지 확인합니다.
  3. 오프라인 상태에서 인앱 이벤트가 발생하면 SDK는 캐시된 규칙 집합과 비교하여 평가하고 즉시 StoreKit 업데이트 API를 호출합니다.
  4. 포스트백 준비 및 전달은 시스템에서 관리하며 비동기식으로 유지됩니다. 앱이 직접 포스트백을 디스패치할 필요가 없습니다.

아래의 Swift 구현은 세밀한 값과 개략적인 값을 계산하고, 창별 잠금 상태를 관리하며, 고정된 스키마 설정을 유지하고, StoreKit이 성공적으로 실행된 경우에만 상태 업데이트를 커밋하는 다중 창 스키마 평가 엔진을 보여줍니다:

import Foundation
import StoreKit

// MARK: - Schema Configuration Models

struct SKANSchemaConfig: Codable {
    let schemaVersion: String
    let appId: String
    let currency: String
    let windows: SchemaWindows

    enum CodingKeys: String, CodingKey {
        case schemaVersion = "schema_version"
        case appId = "app_id"
        case currency, windows
    }
}

struct SchemaWindows: Codable {
    let window1: Window1Config
    let window2: WindowCoarseConfig
    let window3: WindowCoarseConfig

    enum CodingKeys: String, CodingKey {
        case window1 = "window_1"
        case window2 = "window_2"
        case window3 = "window_3"
    }
}

struct Window1Config: Codable {
    let mode: String
    let fineMapping: [FineRule]
    let coarseMapping: CoarseRuleGroup

    enum CodingKeys: String, CodingKey {
        case mode
        case fineMapping = "fine_mapping"
        case coarseMapping = "coarse_mapping"
    }
}

struct FineRule: Codable {
    let event: String
    let minRevenueCents: Int
    let fineValue: Int
    let lock: Bool

    enum CodingKeys: String, CodingKey {
        case event
        case minRevenueCents = "min_revenue_cents"
        case fineValue = "fine_value"
        case lock
    }
}

struct WindowCoarseConfig: Codable {
    let mode: String
    let coarseMapping: [String: CoarseRule]

    enum CodingKeys: String, CodingKey {
        case mode
        case coarseMapping = "coarse_mapping"
    }
}

struct CoarseRuleGroup: Codable {
    let low: CoarseRule
    let medium: CoarseRule
    let high: CoarseRule
}

struct CoarseRule: Codable {
    let events: [String]?
    let lock: Bool?
}

// MARK: - Multi-Window SKAN 4.0 Schema Engine

final class SKANSchemaEngine {

    static let shared = SKANSchemaEngine()
    private init() {}

    private var activeSchema: SKANSchemaConfig?
    private var firstLaunchDate: Date?
    private var lockedWindows = Set<Int>()
    private var lastRecordedFineValue: Int = 0
    private var pinnedSchemaVersion: String?

    /// Initializes the first-launch timestamp anchor independently of remote configuration fetches
    func initializeLifecycleAnchor() {
        let defaults = UserDefaults.standard
        if let storedLaunch = defaults.object(forKey: "skan_first_launch_date") as? Date {
            self.firstLaunchDate = storedLaunch
        } else {
            let now = Date()
            self.firstLaunchDate = now
            defaults.set(now, forKey: "skan_first_launch_date")
        }

        let lockedArray = defaults.array(forKey: "skan_locked_windows") as? [Int] ?? []
        self.lockedWindows = Set(lockedArray)
        self.lastRecordedFineValue = defaults.integer(forKey: "skan_last_fine_value")
        self.pinnedSchemaVersion = defaults.string(forKey: "skan_pinned_schema_version")

        // Restore previously cached schema payload if it matches the pinned version
        if let pinnedVersion = self.pinnedSchemaVersion,
           let cachedData = defaults.data(forKey: "skan_cached_schema_payload"),
           let cachedSchema = try? JSONDecoder().decode(SKANSchemaConfig.self, from: cachedData),
           cachedSchema.schemaVersion == pinnedVersion {
            self.activeSchema = cachedSchema
        }
    }

    /// Loads active schema, persisting the pinned payload to maintain consistency across the 35-day lifecycle
    func configure(schema: SKANSchemaConfig) {
        let defaults = UserDefaults.standard
        if let pinned = pinnedSchemaVersion {
            // If already pinned, accept only schemas matching the pinned version
            if pinned == schema.schemaVersion {
                self.activeSchema = schema
                if let data = try? JSONEncoder().encode(schema) {
                    defaults.set(data, forKey: "skan_cached_schema_payload")
                }
            }
        } else {
            // Pin the initial schema version for this lifecycle
            self.activeSchema = schema
            self.pinnedSchemaVersion = schema.schemaVersion
            defaults.set(schema.schemaVersion, forKey: "skan_pinned_schema_version")
            if let data = try? JSONEncoder().encode(schema) {
                defaults.set(data, forKey: "skan_cached_schema_payload")
            }
        }
    }

    /// Determines the active conversion window based on elapsed time from first launch
    private var currentWindowIndex: Int {
        guard let firstLaunch = firstLaunchDate else { return 0 }
        let elapsedHours = Date().timeIntervalSince(firstLaunch) / 3600.0

        switch elapsedHours {
        case 0.0..<48.0:
            return 1
        case 48.0..<168.0:
            return 2
        case 168.0...840.0:
            return 3
        default:
            return 0 // Window closed (>35 days)
        }
    }

    /// Evaluates an in-app event against the active schema for the current window
    func trackEvent(name: String, revenueCents: Int = 0) {
        guard #available(iOS 16.1, *),
              let schema = activeSchema else { return }

        let window = currentWindowIndex
        guard window >= 1 && window <= 3, !lockedWindows.contains(window) else { return }

        var targetFineValue: Int?
        var targetCoarseValue: SKAdNetwork.CoarseConversionValue?
        var shouldLock = false
        var matchedRule = false

        if window == 1 {
            // Window 1: Evaluate fine-grained rules with highest-threshold precedence
            let matchingFineRules = schema.windows.window1.fineMapping
                .filter { $0.event == name && revenueCents >= $0.minRevenueCents }
                .sorted { $0.minRevenueCents < $1.minRevenueCents }

            if let highestRule = matchingFineRules.last {
                targetFineValue = highestRule.fineValue
                if highestRule.lock { shouldLock = true }
                matchedRule = true
            }

            // Window 1: Evaluate coarse-grained rules explicitly
            if schema.windows.window1.coarseMapping.high.events?.contains(name) == true {
                targetCoarseValue = .high
                matchedRule = true
            } else if schema.windows.window1.coarseMapping.medium.events?.contains(name) == true {
                targetCoarseValue = .medium
                matchedRule = true
            } else if schema.windows.window1.coarseMapping.low.events?.contains(name) == true {
                targetCoarseValue = .low
                matchedRule = true
            }
        } else {
            // Windows 2 & 3: Evaluate coarse rules only
            let coarseConfig = (window == 2) ? schema.windows.window2 : schema.windows.window3
            
            if let highRule = coarseConfig.coarseMapping["high"], highRule.events?.contains(name) == true {
                targetCoarseValue = .high
                if highRule.lock == true { shouldLock = true }
                matchedRule = true
            } else if let medRule = coarseConfig.coarseMapping["medium"], medRule.events?.contains(name) == true {
                targetCoarseValue = .medium
                if medRule.lock == true { shouldLock = true }
                matchedRule = true
            } else if let lowRule = coarseConfig.coarseMapping["low"], lowRule.events?.contains(name) == true {
                targetCoarseValue = .low
                if lowRule.lock == true { shouldLock = true }
                matchedRule = true
            }
        }

        // If no explicit rule matched for this event, do not trigger a StoreKit update
        guard matchedRule else { return }

        let fineToSubmit = targetFineValue ?? (window == 1 ? lastRecordedFineValue : 0)
        let clampedFine = max(0, min(63, fineToSubmit))
        let coarseToSubmit = targetCoarseValue ?? .low

        // Dispatch StoreKit conversion update
        // Note: StoreKit ignores the fineValue parameter after Window 1
        SKAdNetwork.updatePostbackConversionValue(
            clampedFine,
            coarseValue: coarseToSubmit,
            lockWindow: shouldLock
        ) { [weak self] error in
            guard let self = self else { return }
            if let error = error {
                print("StoreKit conversion update failed: \(error.localizedDescription)")
            } else {
                // Commit local state only after StoreKit successfully accepts the update
                DispatchQueue.main.async {
                    if window == 1 {
                        self.lastRecordedFineValue = clampedFine
                        UserDefaults.standard.set(clampedFine, forKey: "skan_last_fine_value")
                    }
                    if shouldLock {
                        self.lockedWindows.insert(window)
                        UserDefaults.standard.set(Array(self.lockedWindows), forKey: "skan_locked_windows")
                    }
                    print("SKAN 4.0 update succeeded: Window=\(window), Fine=\(clampedFine), Coarse=\(coarseToSubmit.rawValue), Locked=\(shouldLock)")
                }
            }
        }
    }
}

SKAN lockWindow 타이밍 및 조기 포스트백 준비

포스트백 준비 가속화를 위한 lockWindow 실행 자동화

lockWindow 매개변수의 운영 메커니즘

앱이 lockWindow: true와 함께 updatePostbackConversionValue(_:coarseValue:lockWindow:)를 호출하면, 해당 업데이트는 활성 창의 최종 전환 값 업데이트가 됩니다. 운영체제는 즉시 포스트백을 준비하고 해당 창의 남은 시간 동안 추가적인 전환 값 업데이트를 무시합니다.

Default Window 1 (No Lock):
[First Launch] ─────────────── 48 Hours Open ───────────────► [Closes] ──► Delay (24-48h) ──► Postback 1

Locked Window 1 (Purchase at Hour 6):
[First Launch] ── 6h (Lock: true) ──► [Conversion Locked / Postback Prepared] ──► Delay (24-48h) ──► Postback 1 Sent Sooner

자동화된 창 잠금의 전략적 트레이드오프

  • 포스트백 디스패치 가속화: 전환을 조기에 마감하면 Apple의 무작위 포스트백 지연이 즉시 시작되어 광고 네트워크에 전환 데이터를 더 빨리 전달할 수 있습니다.
  • 창 독립성: 현재 창을 잠그더라도 다음 창의 시작 시점이 앞당겨지지 않습니다. 창 1이 잠긴 시점과 관계없이 창 2는 여전히 Day 3에 시작됩니다.
  • 관찰 중단: 창이 잠기면 시스템은 해당 전환 창의 남은 기간 동안 후속 전환 값 업데이트 호출을 무시합니다. 인앱 이벤트는 계속 발생할 수 있지만 더 이상 해당 창의 SKAdNetwork 전환 상태를 변경할 수 없습니다.

AdAttributionKit과 SKAN 스키마 조정

Apple의 진화하는 어트리뷰션 스택

Apple은 이제 앱 스토어 및 대체 앱 마켓플레이스의 앱 광고 캠페인을 위해 AdAttributionKit을 권장합니다. SKAdNetwork는 기존 통합 및 상호 운용성을 위해 계속 관련성이 있으므로 동적 매핑 엔진은 비즈니스 규칙 레이어를 프레임워크별 전환 API와 별도로 유지해야 합니다:

  • 공유 값 차원: 두 프레임워크 모두 6비트 세밀한 값(0~63)과 3단계 개략적인 값(low, medium, high)을 평가합니다.
  • 별도의 API 레이어: SKAdNetwork는 SKAdNetwork.updatePostbackConversionValue를 사용하고, AdAttributionKit은 Postback.updateConversionValue를 사용합니다.
  • 브릿징 동작: 통합이 두 프레임워크를 모두 지원하는 경우, 문서화된 SKAdNetwork-to-AdAttributionKit 브릿징 동작을 고려하면서 두 프레임워크의 전환 업데이트 API를 모두 호출하는 것이 좋습니다.

비교 결정 매트릭스: 하드코딩된 클라이언트 로직 대 동적 설정

평가 차원 하드코딩된 클라이언트 측 로직 동적 스키마 설정
스키마 수정 속도 앱 스토어 검토 필요 (며칠~몇 주 소요) 새로운 바이너리 배포 없이 지원되는 규칙 변경에 대한 원격 업데이트
테스트 및 반복 민첩성 높은 마찰 / 높은 엔지니어링 오버헤드 버전 및 코호트가 격리된 규칙을 사용한 제어된 스키마 실험
다중 창 조정 Swift의 복잡한 수동 상태 기계 자동화된 수명 주기 인식 엔진
자동화된 창 잠금 고정되고 유연하지 않은 규칙 트리거 동적 이벤트 트리거 잠금 규칙
프레임워크 간 패리티 프레임워크 간에 단편화된 코드 통합 클라우드 설정 매트릭스

자주 묻는 질문 (FAQ)

사용자가 서로 다른 전환 값에 매핑된 여러벤트를 트리거하면 어떻게 되나요?
SKAdNetwork 4.0에서 Apple은 창 1의 전환 값이 연속적인 호출에 걸쳐 증가하거나 감소할 수 있도록 허용합니다. 그러나 어트리뷰션 스키마는 벤더 디자인 규칙으로 단조 증가를 적용할 수 있으며, 이 경우 클라이언트 SDK는 들어오는 이벤트가 현재 기록된 상태보다 더 높은 값을 생성할 때만 전환 값을 업데이트합니다.
앱이 오프라인 상태일 때 자동화된 스키마가 전환 값을 업데이트할 수 있나요?
네. SDK에 유효한 캐시된 스키마가 있는 경우 새 스키마를 동기식으로 가져오지 않고도 이벤트를 평가하고 StoreKit을 호출할 수 있습니다. SKAdNetwork 포스트백 준비 및 전달은 시스템에서 관리하며 비동기식으로 유지됩니다.
자동화된 스키마는 글로벌 사용자의 통화 변환을 어떻게 처리하나요?
자동화된 스키마 엔진은 모든 인앱 구매 금액을 기기에서 표준 기준 통화(예: USD 센트)로 정규화하거나 수익 버킷 임계값을 평가하기 전에 미리 변환된 정수 값을 전달합니다.

요약 및 결정 프레임워크

SKAdNetwork 전환 값 매핑 자동화는 모바일 바이너리 배포 주기에서 성장 실험을 분리합니다. 중앙 집중형 어트리뷰션 대시보드에서 동적 스키마를 배포하고 SDK 내에서 로컬로 평가함으로써, 엔지니어링 팀은 수익 버킷을 미세 조정하고 퍼널 마일스톤을 최적화하며 자동화된 창 잠금을 설정할 수 있어 앱 스토어 커넥트에 애플리케이션 바이너리를 다시 제출하지 않고도 지원되는 전환 규칙 변경 사항을 적용할 수 있습니다.

애플리케이션 수준의 딥링크 라우팅은 별도의 측정 및 온보딩 레이어로 Apple의 개인정보 보호 어트리뷰션 프레임워크와 함께 작동할 수 있습니다. Openinstall과 같은 플랫폼은 퍼스트 파티 컨텍스트 라우팅 및 지연된 딥링크를 위한 인프라를 제공하여 팀이 웹투앱 전환 퍼널 전반에서 사용자 의도를 유지할 수 있도록 합니다.

개인정보 보호를 준수하는 어트리뷰션 및 딥링크 파이프라인 설정에 대해 자세히 알아보려면 Openinstall 문서를 검토하세요.

관련 자료

  • 개념: 전환 값 스키마, 동적 스키마 매핑, 수익 버킷팅, 창 잠금, 단조성

  • 기술: Apple SKAdNetwork, Apple AdAttributionKit, StoreKit 프레임워크, Openinstall 모바일 SDK

  • 표준: IETF RFC 8259 JSON 사양

  • API: StoreKit updatePostbackConversionValue API, AdAttributionKit Postback.updateConversionValue API

공식 문서

Share this article