서버 대 서버 - SKAdNetwork 3 구현 가이드

문서

SKAdNetwork 4 사용 가능: 최신 SKAdNetwork 버전은 SKAdNetwork 4 구현 가이드를 참조하세요.

SKAdNetwork 3.0 S2S 구현 가이드

서버 간 연동을 통해 개인정보 보호 규정을 준수하는 iOS 어트리뷰션을 위한 Apple의 SKAdNetwork 3.0 프레임워크를 구현하여 사용자 식별 정보를 손상시키지 않고 앱 인스톨 캠페인 성과를 측정할 수 있습니다.


개요

SKAdNetwork란?

SKAdNetwork는 사용자 수준의 식별자 없이도 iOS에서 앱 인스톨 광고 캠페인 전환율을 측정할 수 있는 Apple의 개인정보 보호 어트리뷰션 프레임워크입니다.

이 프레임워크는 앱 스토어 서버를 통해 어트리뷰션을 처리하며, 어트리뷰션 정보를 애드 네트워크에 전송하기 전에 사용자 식별자 및 임시 데이터에서 분리합니다.

Apple 문서: SKAdNetwork 프레임워크 레퍼런스


SKAdNetwork의 작동 방식

어트리뷰션 프로세스는 전적으로 Apple의 인프라를 통해 이루어지며, 캠페인 성과 측정이 가능하면서도 사용자 개인정보 보호가 보장됩니다.

SKAdNetwork Attribution Flow

어트리뷰션 흐름:

  1. 광고 클릭: 사용자가 네트워크, 퍼블리셔, 캠페인 ID와 함께 SKAdNetwork 서명이 포함된 광고를 클릭합니다.
  2. 앱 스토어 열기: 디바이스가 어트리뷰션 데이터를 저장하고 앱 설치를 위해 앱 스토어를 엽니다.
  3. 앱 실행: 사용자가 앱을 처음 설치하고 실행합니다.
  4. 등록: 앱이 SKAdNetwork 프레임워크에 등록되어 설치 성공 신호를 보냅니다.
  5. 전환 가치: 앱이 선택적으로 설치 후 활동을 나타내는 전환 값(0-63)을 업데이트합니다.
  6. 타이머 기간: 디바이스가 첫 실행 또는 마지막 전환 값 업데이트 후 24시간 이상 대기합니다.
  7. 포스트백: 앱스토어는 캠페인 ID, 전환 값, 암호화 서명이 포함된 어트리뷰션 포스트백을 애드 네트워크에 전송합니다.

개인정보 보호 설계:

  • 포스트백에는 디바이스 또는 사용자 식별자가 포함되지 않습니다.
  • 최소 24시간 지연으로 시간적 상관관계 방지
  • 앱은 사용자가 어떤 광고를 클릭했는지 절대 알 수 없음
  • 네트워크는 어떤 특정 사용자가 설치했는지 절대 알 수 없음

기능 및 제한 사항

SKAdNetwork가 제공하는기능

  • 라스트-클릭 어트리뷰션: 사용자 동의 또는 ATT 옵트인 없이 작동합니다.
  • 캠페인 분석: 소스, 캠페인, 퍼블리셔 세분화
  • 전환 값: 설치 후 측정을 위한 64개의 개별 값(0-63)
  • 사기 방지: 암호화 서명으로 어트리뷰션 진위 여부 검증

현재 제한 사항:

  • 사용자 수준 데이터 없음: 개별 사용자 여정을 추적할 수 없음
  • 뷰스루 없음: 클릭 기반 어트리뷰션만 가능
  • 제한된 전환 값: 인스톨당 Singular 6비트 값(0-63)
  • 제한된 세분성: 최대 100개의 캠페인 ID, 광고 그룹 또는 크리에이티브 세분화 없음
  • 긴 코호트 없음: 전환 측정을 위한 제한된 기간
  • 사기 노출: 전환 값 자체에 서명되지 않음, 포스트백이 중복될 수 있음

Singular 리소스

Singular는 SKAdNetwork 구현 및 최적화를 위한 종합적인 리소스를 제공합니다.


Singular SKAdNetwork 솔루션

Singular의 SKAdNetwork 솔루션은 클라이언트 측 구현부터 포스트백 처리 및 캠페인 최적화까지 엔드투엔드 어트리뷰션 관리를 제공합니다.

솔루션 구성 요소

플랫폼 특징

어트리뷰션 및 분석 워크플로우 전반에 걸친 포괄적인 SKAdNetwork 지원.

컴포넌트 기능
클라이언트 측 코드 SKAdNetwork 프레임워크 등록 및 전환 가치 관리를 위한 네이티브 iOS 코드 샘플
포스트백 처리 연동 리포팅을 통해 모든 애드 네트워크의 포스트백을 검증하고 집계합니다.
사기 방지 암호화 서명 검증, 트랜잭션 ID 중복 제거 및 보안 파라미터 검증
전환 관리 설치 후 활동을 전환 값으로 인코딩하기 위한 동적 대시보드 구성
리포팅 세분화된 분석을 위한 마케팅 파라미터를 통한 캠페인 ID 변환 및 보강
파트너 포스트백 파트너 최적화를 위해 이벤트 및 구매으로 전송된 디코딩된 전환 값

S2S 연동 아키텍처

구현 구성 요소

서버 간 SKAdNetwork 연동은 크게 두 가지 구현 영역으로 구성됩니다.

1. 클라이언트 측 구현(필수):

  • 앱 실행 시 SKAdNetwork 프레임워크 등록
  • 설치 후 활동에 기반한 지능형 전환 가치 관리
  • SKAdNetwork 어트리뷰션을 활용한 캠페인 최적화를 위한 필수 요소

2. S2S 연동 업데이트 (선택 사항):

  • SKAdNetwork 메타데이터로 Singular S2S 이벤트 및 세션 강화
  • 구현 검증 및 문제 해결 가능
  • 디버깅을 위한 전환 값 타임스탬프 제공

클라이언트 측 구현

최적의 캠페인 측정을 위해 Singular의 네이티브 iOS 코드 샘플을 사용하여 SKAdNetwork 프레임워크 등록 및 전환 가치 관리를 구현합니다.

구현 책임

핵심 기능

클라이언트 측 코드는 SKAdNetwork 측정을 위한 두 가지 핵심 기능을 처리합니다.

  1. SKAdNetwork 등록: 어트리뷰션을 활성화하기 위해 출시 직후 앱을 프레임워크에 등록합니다.
  2. 전환 가치 관리:
    • Singular 엔드포인트와 동기식으로 통신하여 다음 전환 가치를 수신합니다.
    • 세션, 이벤트, 구매 등을 Singular에 보고합니다.
    • 인스톨 후 구성된 활동을 나타내는 인코딩된 전환 가치 수신
    • SKAdNetwork 메타데이터(첫 번째 통화 타임스탬프, 마지막 통화 타임스탬프, 현재 값)를 수집합니다.

SKAdNetwork 인터페이스

메소드 정의

인터페이스에는 프레임워크 등록, 세션 추적, 이벤트 추적, 구매 측정을 위한 메소드가 포함되어 있습니다.

SKANSnippet.h
//  SKANSnippet.h

#import <Foundation/Foundation.h>

@interface SKANSnippet : NSObject

// Register for SKAdNetwork attribution.
// Call this method as soon as possible once app is launched
+ (void)registerAppForAdNetworkAttribution;

// Track retention and cohorts by calling after each session.
// Reports session details and updates conversion value if needed.
// Conversion value updates only if new value greater than previous.
// Optional callback runs once conversion value updated.
+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler;

// Track conversion events by calling after each event.
// Reports event details and updates conversion value if needed.
// Optional callback runs once conversion value updated.
+ (void)updateConversionValueAsync:(NSString*)eventName
               withCompletionHandler:(void(^)(int, NSError*))handler;

// Track revenue by calling before each revenue event.
// Updates total revenue for next conversion value calculation.
// Note:
// 1. Call before 'updateConversionValueAsync' to ensure revenue included
// 2. Avoid calling on event retries to prevent double-counting
+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency;

// Gets current conversion value (nil if none set)
+ (NSNumber *)getConversionValue;

@end

등록 메소드

registerAppForAdNetworkAttribution 24시간 어트리뷰션 타이머를 시작하여 앱을 SKAdNetwork 프레임워크에 등록합니다.

타이머 동작:

  • 타이머 만료 0~24시간 후 디바이스에서 설치 알림을 전송합니다.
  • 전환 값을 더 높은 값으로 업데이트하면 타이머가 새로운 24시간 간격으로 재설정됩니다.
  • Apple 참조: updateConversionValue 문서

전환 값 메서드

메서드는 설치 후 활동 및 구성된 전환 모델을 기반으로 전환 값을 계산하고 업데이트합니다.

지원되는 활동:

  • 세션: 리텐션 측정에 중요
  • 전환 이벤트: 설치 후 이벤트 측정에 중요
  • 구매 이벤트: 매출 측정에 필수

인터페이스 구현

전체 구현 코드

전체 구현은 등록, 전환 관리 및 Singular API 통신을 처리합니다.

SKANSnippet.m
//  SKANSnippet.m

#import "SKANSnippet.h"
#import <StoreKit/SKAdNetwork.h>

#define SESSION_EVENT_NAME @"__SESSION__" 
#define SINGULAR_API_URL @"https://sdk-api-v1.singular.net/api/v1/conversion_value"

// Keys for UserDefaults storage
#define CONVERSION_VALUE_KEY @"skan_conversion_value"
#define FIRST_SKAN_CALL_TIMESTAMP @"skan_first_call_to_skadnetwork_timestamp"
#define LAST_SKAN_CALL_TIMESTAMP @"skan_last_call_to_skadnetwork_timestamp"
#define TOTAL_REVENUE_BY_CURRENCY_KEY @"skan_total_revenue_by_currency"
#define SECONDS_PER_DAY 86400

static NSLock *lockObject;

@implementation SKANSnippet

+ (void)registerAppForAdNetworkAttribution {
    if ([SKANSnippet getFirstSkanCallTimestamp] != 0) {
        return;
    }

    if (@available(iOS 11.3, *)) {
        [SKAdNetwork registerAppForAdNetworkAttribution];
        [SKANSnippet setFirstSkanCallTimestamp];
        [SKANSnippet setLastSkanCallTimestamp];
    }
}

+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler {
    [SKANSnippet updateConversionValueAsync:SESSION_EVENT_NAME withCompletionHandler:handler];
}

+ (void)updateConversionValueAsync:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
    if (@available(iOS 14, *)) {
        if ([SKANSnippet isSkanUpdateWindowOver]) {
            return;
        }
        
        [SKANSnippet getConversionValueFromServer:eventName withCompletionHandler:handler];
    }
}

+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency {
    if (@available(iOS 14, *)) {
        
        // Update total revenues
        if (amount != 0 && currency) {
            NSMutableDictionary* revenues = [[SKANSnippet getTotalRevenue] mutableCopy];
            NSNumber* currentRevenue = @(0);
            
            if ([revenues objectForKey:currency]) {
                currentRevenue = [revenues objectForKey:currency];
            }
            
            currentRevenue = @([currentRevenue floatValue] + amount);
            [revenues setObject:currentRevenue forKey:currency];
            
            [SKANSnippet setTotalRevenue:revenues];
        }
    }
}

+ (NSNumber *)getConversionValue {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:CONVERSION_VALUE_KEY]) {
        return nil;
    }
    return @([userDefaults integerForKey:CONVERSION_VALUE_KEY]);
}

+ (void)getConversionValueFromServer:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
    if (!lockObject) {
        lockObject = [NSLock new];
    }
    
    // Making the lock async so it will not freeze the calling thread
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
        
        [lockObject lock];
        
        NSURLComponents *components = [NSURLComponents componentsWithString:SINGULAR_API_URL];
        
        NSString* bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
        
        components.queryItems = @[
            [NSURLQueryItem queryItemWithName:@"a" value:@"YOUR_SDK_KEY"],
            [NSURLQueryItem queryItemWithName:@"i" value:bundleIdentifier],
            [NSURLQueryItem queryItemWithName:@"app_v" value:@"YOUR_APP_VERSION"],
            [NSURLQueryItem queryItemWithName:@"n" value:eventName],
            [NSURLQueryItem queryItemWithName:@"p" value:@"iOS"],
            [NSURLQueryItem queryItemWithName:@"idfv" value:@"YOUR_IDFV"],
            [NSURLQueryItem queryItemWithName:@"idfa" value:@"YOUR_IDFA"],
            [NSURLQueryItem queryItemWithName:@"conversion_value"  value:[[SKANSnippet getConversionValue] stringValue]],
            [NSURLQueryItem queryItemWithName:@"total_revenue_by_currency"  value:[SKANSnippet dictionaryToJsonString:[SKANSnippet getTotalRevenue]]],
            [NSURLQueryItem queryItemWithName:@"first_call_to_skadnetwork_timestamp"  value:[NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]]],
            [NSURLQueryItem queryItemWithName:@"last_call_to_skadnetwork_timestamp"  value:[NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]],
        ];
        
        [[[NSURLSession sharedSession] dataTaskWithURL:components.URL
                                     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [lockObject unlock];
                if (handler) {
                    handler(-1, error);
                }
                return;
            }
            
            NSDictionary* parsedResponse = [SKANSnippet jsonDataToDictionary:data];
            
            if (!parsedResponse) {
                [lockObject unlock];
                if (handler) {
                    handler(-1, [NSError errorWithDomain:bundleIdentifier
                                                    code:0
                                                userInfo:@{NSLocalizedDescriptionKey:@"Failed parsing server response"}]);
                }
                return;
            }
            
            NSNumber *conversionValue = [parsedResponse objectForKey:@"conversion_value"];
            
            if (!conversionValue) {
                [lockObject unlock];
                NSString *status = [parsedResponse objectForKey:@"status"];
                
                if (!status || ![status isEqualToString:@"ok"]) {
                    if (handler) {
                    NSString *reason = [parsedResponse objectForKey:@"reason"];
                        if (!reason) {
                            reason = @"Got error from server";
                        }
                        
                        handler(-1, [NSError errorWithDomain:bundleIdentifier
                                                        code:0
                                                    userInfo:@{NSLocalizedDescriptionKey:reason}]);
                    }
                }
                return;
            }
            
            NSNumber* currentValue = [SKANSnippet getConversionValue];
            if ([conversionValue intValue] <= [currentValue intValue]) {
                [lockObject unlock];
                return;
            }
            
            [SKANSnippet setConversionValue:[conversionValue intValue]];
            [SKANSnippet setLastSkanCallTimestamp];
            
            if (![SKANSnippet getFirstSkanCallTimestamp]) {
                [SKANSnippet setFirstSkanCallTimestamp];
            }
            
            [lockObject unlock];
            
            if (handler) {
                handler([conversionValue intValue], error);
            }
        }] resume];
    });
}

+ (BOOL)isSkanUpdateWindowOver {        
    NSInteger timeDiff = [SKANSnippet getCurrentUnixTimestamp] - [SKANSnippet getLastSkanCallTimestamp];
    return SECONDS_PER_DAY <= timeDiff;
}

@end

유틸리티 방법

메타데이터 관리

유틸리티 메소드는 전환 가치 계산에 중요한 UserDefaults의 SKAdNetwork 메타데이터 저장소를 관리합니다.

중요 구현: 이 유틸리티는 정확한 전환 가치 산정에 필요한 메타데이터를 유지 관리합니다. 필요한 경우가 아니면 수정하지 마세요.

Utility Methods
+ (NSInteger)getFirstSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    return [userDefaults integerForKey:FIRST_SKAN_CALL_TIMESTAMP];
}

+ (NSInteger)getLastSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    return [userDefaults integerForKey:LAST_SKAN_CALL_TIMESTAMP];
}

+ (NSDictionary*)getTotalRevenue {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY]) {
        return [NSDictionary new];
    }
    return [userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY];
}

+ (NSInteger)getCurrentUnixTimestamp {
    return [[NSDate date]timeIntervalSince1970];
}

+ (void)setConversionValue:(int)value {
    if (@available(iOS 14.0, *)) {
        if (value <= [[SKANSnippet getConversionValue] intValue]) {
            return;
        }
        
        [SKAdNetwork updateConversionValue:value];
        
        NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setInteger:value forKey:CONVERSION_VALUE_KEY];
        [userDefaults synchronize];
    }
}

+ (void)setFirstSkanCallTimestamp {
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp] 
forKey:FIRST_SKAN_CALL_TIMESTAMP]; [userDefaults synchronize]; } + (void)setLastSkanCallTimestamp { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp]
forKey:LAST_SKAN_CALL_TIMESTAMP]; [userDefaults synchronize]; } + (void)setTotalRevenue:(NSDictionary *)values { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setObject:values forKey:TOTAL_REVENUE_BY_CURRENCY_KEY]; [userDefaults synchronize]; } + (NSString*)dictionaryToJSONString:(NSDictionary*)dictionary { if (!dictionary || [dictionary count] == 0){ return @"{}"; } NSError *error; NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error]; if (error || !JSONData) { return @"{}"; } return [[NSString alloc] initWithData:JSONData
encoding:NSUTF8StringEncoding]; } + (NSDictionary*)JSONDataToDictionary:(NSData*)JSONData { if (!JSONData) { return nil; } NSError * error; NSDictionary * parsedData = [NSJSONSerialization
JSONObjectWithData:JSONData options:kNilOptions error:&error]; if (error || !parsedData) { return nil; } return parsedData; } @end

S2S 연동 업데이트

구현 검증 및 문제 해결을 위해 선택적으로 SKAdNetwork 메타데이터와의 서버 간 연동을 향상시킵니다.

필수 업데이트

전환 가치 응답

전환 모델이 활성화되면 S2S 엔드포인트는 클라이언트 측 업데이트를 위한 다음 값이 포함된 conversion_value 정숫값 필드를 반환합니다.

응답 처리: S2S 엔드포인트 응답에서 conversion_value을 파싱하여 클라이언트 측 코드를 통해 SKAdNetwork 프레임워크에 적용합니다.


선택적 개선 사항

메타데이터 파라미터

연동을 검증하고 구현 문제를 해결하기 위해 S2S 세션 및 이벤트 요청에 SKAdNetwork 메타데이터를 추가합니다.

파라미터 설명
skan_conversion_value 요청 시점의 최신 전환 값
skan_first_call_timestamp SKAdNetwork API에 대한 첫 번째 호출의 유닉스 타임스탬프
skan_last_call_timestamp SKAdNetwork API에 대한 마지막 호출의 유닉스 타임스탬프

메타데이터 추출 코드

S2S 전송을 위한 유틸리티 메소드를 사용하여 SKAdNetwork 메타데이터 값을 추출합니다.

Objective-C
NSDictionary *skanMetadata = @{
    @"skan_conversion_value": 
        [[SKANSnippet getConversionValue] stringValue],
    @"skan_first_call_timestamp": 
        [NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]],
    @"skan_last_call_timestamp": 
        [NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]
};

// Forward skanMetadata to your server for S2S API enrichment

파라미터를 서버 측으로 전달하고 Singular S2S API 요청에 추가합니다. 전체 파라미터 문서: S2S API 레퍼런스


연동 흐름

클라이언트 측 전환 관리부터 포스트백 처리까지 S2S 고객을 위한 완벽한 SKAdNetwork 플로우입니다.

SKAdNetwork S2S Integration Flow

흐름 단계

엔드 투 엔드 프로세스

  1. 전환 가치 요청: 앱 코드가 Singular 엔드포인트와 동기식으로 통신하여 세션, 이벤트, 매출에 기반한 최신 전환 가치를 가져옵니다.
  2. 프레임워크 업데이트: 앱이 수신된 전환 값으로 SKAdNetwork 프레임워크를 업데이트합니다.
  3. 메타데이터 보강: 앱이 검증을 위해 SKAdNetwork 메타데이터로 S2S 이벤트 및 세션을 보강합니다.
  4. 타이머 만료: 마지막 업데이트 후 24시간 이상 경과 후, SKAdNetwork가 광고 네트워크에 포스트백을 전송합니다.
  5. 포스트백 포워딩: 네트워크가 포스트백을 Singular(보안 설정 또는 일반)로 포워딩합니다.
  6. 포스트백 처리: Singular가 포스트백을 처리합니다:
    • 암호화 서명 검증
    • 구성된 모델을 사용하여 전환 값 디코딩
    • 파트너 연동에 기반한 마케팅 파라미터로 강화
    • 포스트백을 통해 디코딩된 데이터를 BI 및 파트너에게 전송

데이터 분리: 테스트 및 검증 과정에서 기존 데이터 세트와의 혼용을 방지하기 위해 별도의 보고서, API, ETL 테이블, 포스트백을 통해 SKAdNetwork 데이터(인스톨 및 디코딩된 이벤트)에 액세스합니다.


앱 라이프사이클 구현

완벽한 어트리뷰션 커버리지를 위해 적절한 앱 라이프사이클 지점에서 SKAdNetwork 방식을 연동합니다.

구현 예시

메소드 배치

Objective-C
// On app launch (in applicationDidFinishLaunching)
[SKANSnippet registerAppForAdNetworkAttribution];

// After each session handled
[SKANSnippet updateConversionValueAsync:^(int value, NSError *error) {
    if (error) {
        NSLog(@"Conversion value update failed: %@", error);
    } else {
        NSLog(@"Conversion value updated to: %d", value);
    }
}];

// After handling non-revenue events
[SKANSnippet updateConversionValueAsync:@"event_name" 
                   withCompletionHandler:^(int value, NSError *error) {
    if (error) {
        NSLog(@"Event conversion value update failed: %@", error);
    } else {
        NSLog(@"Event conversion value updated to: %d", value);
    }
}];

// After handling revenue events
[SKANSnippet updateRevenue:15.53 andCurrency:@"USD"];
[SKANSnippet updateConversionValueAsync:@"revenue_event_name" 
                   withCompletionHandler:^(int value, NSError *error) {
    if (error) {
        NSLog(@"Revenue conversion value update failed: %@", error);
    } else {
        NSLog(@"Revenue conversion value updated to: %d", value);
    }
}];

구현 테스트

프로덕션 배포 전에 Singular의 테스트 엔드포인트를 사용하여 SKAdNetwork 구현을 검증합니다.

테스트 환경

테스트 엔드포인트 구성

프로덕션 전환 관리 엔드포인트를 테스트 엔드포인트로 교체하여 앱 흐름을 시뮬레이션합니다.

테스트 엔드포인트 URL:

https://skadnetwork-testing.singular.net/api/v1/conversion_value
  • 테스트 엔드포인트에는 API 키가 필요하지 않습니다.
  • 전환 값 0-2를 순차적으로 반환합니다.
  • 세 번째 호출 후 빈 문자열 반환

테스트 절차

유효성 검사 단계

  1. 엔드포인트를 업데이트합니다: SINGULAR_API_URL 상수를 테스트 엔드포인트 URL로 바꿉니다.
  2. 전환 값을 초기화합니다: 기본 전환 값이 0으로 초기화되었는지 확인합니다.
  3. 이벤트 생성: 앱 내에서 3가지 다른 이벤트를 트리거합니다.
  4. 업데이트 확인: 각 이벤트 후 호출된 updateConversionValueAsync 확인
  5. 로그 값: 반환된 전환 값을 기록하여 올바른 진행을 확인합니다(0 → 1 → 2).
  6. 완료 확인: 세 번째 호출 후 빈 응답 및 최종 값 유지 확인

예상 동작:

  • 첫 번째 이벤트: 전환 값 0을 수신합니다.
  • 두 번째 이벤트: 전환 값 1 수신
  • 세 번째 이벤트: 전환 값 2 수신
  • 네 번째 이상의 이벤트: 빈 응답, 값 2 지속

코드 변경 로그

시간이 지남에 따라 적용된 코드 샘플 업데이트 및 중요 수정 사항을 추적합니다.

버전 기록

날짜 변경 사항
2020년 10월 1일
  • 중요] 수정: getConversionValueFromServer 초기화의 잠금 메커니즘 수정
  • 개선되었습니다: 이제 오류 처리 시 서버 응답 실패 이유가 반환됩니다.
2020년 9월 23일
  • 수정 [중요]: setConversionValue 이제 updateConversionValue을 올바르게 호출합니다.
  • 개선되었습니다: 서버에서 전환 값 검색 시 오류 처리 개선
2020년 9월 15일
  • 변경됨 [중요]:getConversionValue기본값이 설정되지 않은 경우 null에서 0으로 변경되었습니다.
  • 개선되었습니다: 구매 보고 및 처리 개선
  • 개선되었습니다: 비동기 전환 값 검색 구현