Skip to content

푸시 분석 및 커스텀 이벤트 로깅

이 페이지에서는 네이티브 푸시 분석(열기, 영향받은 열기, 캠페인 보고서)과 푸시 페이로드에서의 커스텀 데이터 로깅(커스텀 이벤트 및 속성) 워크플로를 다룹니다. 이 가이드를 통해 사용 사례에 해당하는 워크플로를 확인하고 플랫폼에 맞는 단계를 따르세요.

필수 조건

시작하기 전에 플랫폼에 대한 초기 푸시 알림 통합을 완료하세요:

네이티브 푸시 분석 vs. 커스텀 이벤트 로깅

다음 워크플로는 각각 다른 보고 화면을 사용합니다.

Braze가 자동으로 기록하는 항목

SDK 통합이 구성되면 Braze는 푸시 열기 및 영향받은 열기를 포함한 핵심 채널 상호작용 데이터를 자동으로 기록합니다. 표준 푸시 분석에는 추가 코드가 필요하지 않습니다. 자동으로 수집되는 데이터의 전체 목록은 SDK 데이터 수집을 참조하세요.

자세한 내용은 다음을 참조하세요:

커스텀 푸시 처리 시 네이티브 푸시 분석 유지

여러 푸시 공급자를 통합하거나, 추가 페이로드 데이터를 처리하거나, 커스텀 알림 표시 로직을 구현해야 할 때 커스텀 푸시 핸들러를 사용할 수 있습니다. 커스텀 푸시 핸들러를 사용하는 경우에도 푸시 페이로드를 Braze SDK 메서드에 전달해야 합니다. 이를 통해 Braze가 내장된 추적 데이터를 추출하고 네이티브 푸시 분석(열기, 영향받은 열기, 전달 측정기준)을 기록할 수 있습니다.

커스텀 FirebaseMessagingService가 있는 경우, onMessageReceived 메서드 내에서 BrazeFirebaseMessagingService.handleBrazeRemoteMessage(...)를 호출하세요. FirebaseMessagingService 서브클래스는 Android 시스템에 의해 플래그 지정되거나 종료되지 않도록 호출 후 9초 이내에 실행을 완료해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
public class MyFirebaseMessagingService extends FirebaseMessagingService {
  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);
    if (BrazeFirebaseMessagingService.handleBrazeRemoteMessage(this, remoteMessage)) {
      // Braze processed a Braze push payload.
    } else {
      // Non-Braze payload: pass to your other handlers.
    }
  }
}

전체 구현 예제는 Braze Android SDK Firebase 푸시 샘플 앱을 참조하세요.

수동 푸시 통합에서는 백그라운드 및 사용자 알림 콜백을 Braze에 전달합니다.

백그라운드 알림:

1
2
3
4
5
6
7
if let braze = AppDelegate.braze, braze.notifications.handleBackgroundNotification(
  userInfo: userInfo,
  fetchCompletionHandler: completionHandler
) {
  return
}
completionHandler(.noData)

사용자 알림 응답:

1
2
3
4
5
6
7
if let braze = AppDelegate.braze, braze.notifications.handleUserNotification(
  response: response,
  withCompletionHandler: completionHandler
) {
  return
}
completionHandler()

포그라운드 알림:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func userNotificationCenter(
  _ center: UNUserNotificationCenter,
  willPresent notification: UNNotification,
  withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
  if let braze = AppDelegate.braze {
    braze.notifications.handleForegroundNotification(notification: notification)
  }
  if #available(iOS 14.0, *) {
    completionHandler([.banner, .list, .sound])
  } else {
    completionHandler([.alert, .sound])
  }
}

전체 구현 예제는 Braze Swift SDK 수동 푸시 샘플(AppDelegate.swift)을 참조하세요.

웹 푸시의 경우, 웹 푸시 알림에 설명된 대로 서비스 워커와 SDK 초기화를 구성하세요.

더 많은 코드 샘플은 Braze Web SDK 리포지토리를 참조하세요.

푸시 페이로드에서 커스텀 데이터 로깅

비즈니스 로직에 연결된 커스텀 이벤트나 속성과 같이 푸시 페이로드 키-값 페어에서 추가 데이터를 기록해야 할 때 이 섹션을 사용하세요.

커스텀 이벤트에 대한 자세한 내용은 커스텀 이벤트를 참조하세요. SDK 메서드를 통해 커스텀 이벤트를 기록하려면 커스텀 이벤트 로깅을 참조하세요.

옵션 A: /users/track 엔드포인트로 기록

/users/track 엔드포인트를 호출하여 실시간으로 분석을 기록할 수 있습니다.

고객 프로필을 식별하려면 푸시 페이로드 키-값 페어에 braze_id를 포함하세요.

옵션 B: 앱 실행 후 SDK 메서드로 기록

페이로드 데이터를 로컬에 저장한 후 앱이 초기화된 후 SDK 메서드를 통해 커스텀 이벤트와 속성을 기록할 수도 있습니다. 이 접근 방식은 분석 데이터를 먼저 저장한 후 다음 앱 실행 시 전송하는 알림 콘텐츠 확장 플로우에서 일반적으로 사용됩니다.

알림 콘텐츠 확장에서 로깅(Swift)

다음 단계에서는 Swift 알림 콘텐츠 확장에서 커스텀 이벤트, 커스텀 속성 및 사용자 속성을 저장하고 전송하는 방법을 다룹니다.

1단계: Xcode에서 앱 그룹 구성

Xcode에서 메인 앱 타겟에 App Groups 기능을 추가합니다. App Groups를 켜고 +를 클릭하여 새 그룹을 추가합니다. 앱의 번들 ID를 사용하여 그룹 식별자를 생성합니다(예: group.com.company.appname.xyz). 메인 앱 타겟과 콘텐츠 확장 타겟 모두에서 App Groups를 켭니다.

메인 앱과 알림 확장에 대해 App Groups 기능이 활성화된 Xcode

2단계: 기록할 항목 선택

스니펫을 구현하기 전에 기록할 분석 카테고리를 선택하세요:

  • 커스텀 이벤트: 사용자가 수행하는 동작(예: 플로우 완료 또는 특정 UI 요소 탭). 동작 기반 트리거, 세분화 및 이벤트 분석에 커스텀 이벤트를 사용합니다. 자세한 내용은 커스텀 이벤트커스텀 이벤트 로깅을 참조하세요.
  • 커스텀 속성: 정의하고 시간이 지남에 따라 업데이트하는 프로필 필드(예: plan_tier 또는 preferred_language). 자세한 내용은 커스텀 속성사용자 속성 설정을 참조하세요.
  • 사용자 속성: 표준 프로필 필드(예: 이메일, 이름, 전화번호). 샘플 코드에서는 타입이 지정된 UserAttribute 모델로 표현된 후 Braze 사용자 필드에 매핑됩니다.

이 섹션의 헬퍼 파일(RemoteStorage, UserAttribute, EventName Dictionary)은 이 샘플 구현에서 사용하는 로컬 유틸리티 파일입니다. 내장 SDK 클래스가 아닙니다. 페이로드에서 파생된 데이터를 UserDefaults에 저장하고, 보류 중인 사용자 업데이트를 위한 타입 모델을 정의하며, 이벤트 페이로드 구성을 표준화합니다. 로컬 데이터 저장 동작에 대한 자세한 내용은 스토리지를 참조하세요.

커스텀 이벤트 저장

사전을 빌드하고, 메타데이터를 채우고, 헬퍼 파일로 저장하여 분석 페이로드를 생성합니다.

  1. 이벤트 메타데이터로 사전을 초기화합니다.
  2. 이벤트 데이터를 검색하고 저장하기 위해 userDefaults를 초기화합니다.
  3. 기존 배열이 있으면 추가하고 저장합니다.
  4. 배열이 없으면 새 배열을 저장합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func saveCustomEvent(with properties: [String: Any]? = nil) {
  // 1
  let customEventDictionary = Dictionary(eventName: "YOUR-EVENT-NAME", properties: properties)
  
  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)
  
  // 3
  if var pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] {
    pendingEvents.append(contentsOf: [customEventDictionary])
    remoteStorage.store(pendingEvents, forKey: .pendingCustomEvents)
  } else {
    // 4
    remoteStorage.store([customEventDictionary], forKey: .pendingCustomEvents)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)saveCustomEvent:(NSDictionary<NSString *, id> *)properties {
  // 1
  NSDictionary<NSString *, id> *customEventDictionary = [[NSDictionary alloc] initWithEventName:@"YOUR-EVENT-NAME" properties:properties];
  
  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingEvents = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomEvents] mutableCopy];
  
  // 3
  if (pendingEvents) {
    [pendingEvents addObject:customEventDictionary];
    [remoteStorage store:pendingEvents forKey:RemoteStorageKeyPendingCustomEvents];
  } else {
    // 4
    [remoteStorage store:@[ customEventDictionary ] forKey:RemoteStorageKeyPendingCustomEvents];
  }
}

Braze에 커스텀 이벤트 전송

SDK 초기화 직후에 저장된 분석을 기록합니다.

  1. 보류 중인 이벤트를 순회합니다.
  2. 각 이벤트의 키-값 페어를 순회합니다.
  3. event_name 키를 확인합니다.
  4. 나머지 키-값 페어를 properties 사전에 추가합니다.
  5. 각 커스텀 이벤트를 기록합니다.
  6. 스토리지에서 보류 중인 이벤트를 제거합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func logPendingCustomEventsIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] else { return }
  
  // 1
  for event in pendingEvents {
    var eventName: String?
    var properties: [AnyHashable: Any] = [:]
    
    // 2
    for (key, value) in event {
      if key == "event_name" {
        // 3
        if let eventNameValue = value as? String {
          eventName = eventNameValue
        } else {
          print("Invalid type for event_name key")
        }
      } else {
        // 4
        properties[key] = value
      }
    }
    // 5
    if let eventName = eventName {
      AppDelegate.braze?.logCustomEvent(name: eventName, properties: properties)
    }
  }
  
  // 6
  remoteStorage.removeObject(forKey: .pendingCustomEvents)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)logPendingEventsIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingEvents = [remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomEvents];
  
  // 1
  for (NSDictionary<NSString *, id> *event in pendingEvents) {
    NSString *eventName = nil;
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    
    // 2
    for (NSString* key in event) {
      if ([key isEqualToString:@"event_name"]) {
        // 3
        if ([[event objectForKey:key] isKindOfClass:[NSString class]]) {
          eventName = [event objectForKey:key];
        } else {
          NSLog(@"Invalid type for event_name key");
        }
      } else {
        // 4
        properties[key] = event[key];
      }
    }
    // 5
    if (eventName != nil) {
      [AppDelegate.braze logCustomEvent:eventName properties:properties];
    }
  }
  
  // 6
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingCustomEvents];
}

커스텀 속성 저장

분석 사전을 처음부터 생성한 후 저장합니다.

  1. 속성 메타데이터로 사전을 초기화합니다.
  2. 속성 데이터를 검색하고 저장하기 위해 userDefaults를 초기화합니다.
  3. 기존 배열이 있으면 추가하고 저장합니다.
  4. 배열이 없으면 새 배열을 저장합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func saveCustomAttribute() {
  // 1
  let customAttributeDictionary: [String: Any] = ["YOUR-CUSTOM-ATTRIBUTE-KEY": "YOUR-CUSTOM-ATTRIBUTE-VALUE"]
  
  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)
  
  // 3
  if var pendingAttributes = remoteStorage.retrieve(forKey: .pendingCustomAttributes) as? [[String: Any]] {
    pendingAttributes.append(contentsOf: [customAttributeDictionary])
    remoteStorage.store(pendingAttributes, forKey: .pendingCustomAttributes)
  } else {
    // 4
    remoteStorage.store([customAttributeDictionary], forKey: .pendingCustomAttributes)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)saveCustomAttribute {
  // 1
  NSDictionary<NSString *, id> *customAttributeDictionary = @{ @"YOUR-CUSTOM-ATTRIBUTE-KEY": @"YOUR-CUSTOM-ATTRIBUTE-VALUE" };
  
  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingAttributes = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomAttributes] mutableCopy];
  
  // 3
  if (pendingAttributes) {
    [pendingAttributes addObject:customAttributeDictionary];
    [remoteStorage store:pendingAttributes forKey:RemoteStorageKeyPendingCustomAttributes];
  } else {
    // 4
    [remoteStorage store:@[ customAttributeDictionary ] forKey:RemoteStorageKeyPendingCustomAttributes];
  }
}

Braze에 커스텀 속성 전송

SDK 초기화 직후에 저장된 분석을 기록합니다.

  1. 보류 중인 속성을 순회합니다.
  2. 각 키-값 페어를 순회합니다.
  3. 각 커스텀 속성 키와 값을 기록합니다.
  4. 스토리지에서 보류 중인 속성을 제거합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func logPendingCustomAttributesIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingAttributes = remoteStorage.retrieve(forKey: .pendingCustomAttributes) as? [[String: Any]] else { return }
     
  // 1
  pendingAttributes.forEach { setCustomAttributesWith(keysAndValues: $0) }
  
  // 4
  remoteStorage.removeObject(forKey: .pendingCustomAttributes)
}
   
func setCustomAttributesWith(keysAndValues: [String: Any]) {
  // 2
  for (key, value) in keysAndValues {
    // 3
    if let value = value as? [String] {
      setCustomAttributeArrayWithKey(key, andValue: value)
    } else {
      setCustomAttributeWithKey(key, andValue: value)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)logPendingCustomAttributesIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingAttributes = [remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomAttributes];
   
  // 1
  for (NSDictionary<NSString*, id> *attribute in pendingAttributes) {
    [self setCustomAttributeWith:attribute];
  }
  
  // 4
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingCustomAttributes];
}
 
- (void)setCustomAttributeWith:(NSDictionary<NSString *, id> *)keysAndValues {
  // 2
  for (NSString *key in keysAndValues) {
    // 3
    [self setCustomAttributeWith:key andValue:[keysAndValues objectForKey:key]];
  }
}

사용자 속성 저장

사용자 속성을 저장할 때 업데이트되는 사용자 필드(email, first_name, phone_number 등)를 캡처하는 커스텀 오브젝트를 생성합니다. 이 오브젝트는 UserDefaults를 통해 저장 및 검색할 수 있어야 합니다. 예제는 헬퍼 파일 탭의 UserAttribute 헬퍼 파일을 참조하세요.

  1. 해당 타입으로 인코딩된 UserAttribute 오브젝트를 초기화합니다.
  2. 데이터를 검색하고 저장하기 위해 userDefaults를 초기화합니다.
  3. 기존 배열이 있으면 추가하고 저장합니다.
  4. 배열이 없으면 새 배열을 저장합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func saveUserAttribute() {
  // 1
  guard let data = try? PropertyListEncoder().encode(UserAttribute.email("USER-ATTRIBUTE-VALUE")) else { return }
  
  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)
  
  // 3
  if var pendingAttributes = remoteStorage.retrieve(forKey: .pendingUserAttributes) as? [Data] {
    pendingAttributes.append(contentsOf: [data])
    remoteStorage.store(pendingAttributes, forKey: .pendingUserAttributes)
  } else {
    // 4
    remoteStorage.store([data], forKey: .pendingUserAttributes)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)saveUserAttribute {
  // 1
  UserAttribute *userAttribute = [[UserAttribute alloc] initWithUserField:@"USER-ATTRIBUTE-VALUE" attributeType:UserAttributeTypeEmail];
   
  NSError *error;
  NSData *data = [NSKeyedArchiver archivedDataWithRootObject:userAttribute requiringSecureCoding:YES error:&error];
  
  if (error != nil) {
    // log error
  }
  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingAttributes = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingUserAttributes] mutableCopy];
  
  // 3
  if (pendingAttributes) {
    [pendingAttributes addObject:data];
    [remoteStorage store:pendingAttributes forKey:RemoteStorageKeyPendingUserAttributes];
  } else {
    // 4
    [remoteStorage store:@[data] forKey:RemoteStorageKeyPendingUserAttributes];
  }
}

Braze에 사용자 속성 전송

SDK 초기화 직후에 저장된 분석을 기록합니다.

  1. pendingAttributes 데이터를 순회합니다.
  2. UserAttribute를 디코딩합니다.
  3. 속성 타입에 따라 사용자 필드를 설정합니다.
  4. 스토리지에서 보류 중인 사용자 속성을 제거합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func logPendingUserAttributesIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingAttributes = remoteStorage.retrieve(forKey: .pendingUserAttributes) as? [Data] else { return }
  
  // 1
  for attributeData in pendingAttributes {
    // 2
    guard let userAttribute = try? PropertyListDecoder().decode(UserAttribute.self, from: attributeData) else { continue }
    
    // 3
    switch userAttribute {
    case .email(let email):
      AppDelegate.braze?.user.set(email: email)
    }
  }
  // 4
  remoteStorage.removeObject(forKey: .pendingUserAttributes)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)logPendingUserAttributesIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingAttributes = [remoteStorage retrieveForKey:RemoteStorageKeyPendingUserAttributes];
  
  // 1
  for (NSData *attributeData in pendingAttributes) {
    NSError *error;
    
    // 2
    UserAttribute *userAttribute = [NSKeyedUnarchiver unarchivedObjectOfClass:[UserAttribute class] fromData:attributeData error:&error];
    
    if (error != nil) {
      // log error
    }
    
    // 3
    if (userAttribute) {
      switch (userAttribute.attributeType) {
        case UserAttributeTypeEmail:
          [self user].email = userAttribute.userField;
          break;
      }
    }
  }
  // 4
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingUserAttributes];
}

RemoteStorage 헬퍼 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
enum RemoteStorageKey: String, CaseIterable {
   
  // MARK: - Notification Content Extension Analytics
  case pendingCustomEvents = "pending_custom_events"
  case pendingCustomAttributes = "pending_custom_attributes"
  case pendingUserAttributes = "pending_user_attributes"
}
 
enum RemoteStorageType {
  case standard
  case suite
}
 
class RemoteStorage: NSObject {
  private var storageType: RemoteStorageType = .standard
  private lazy var defaults: UserDefaults = {
    switch storageType {
    case .standard:
      return .standard
    case .suite:
      // Use the App Group identifier you created in Step 1.
      return UserDefaults(suiteName: "group.com.company.appname.xyz")!
    }
  }()
   
  init(storageType: RemoteStorageType = .standard) {
    self.storageType = storageType
  }
   
  func store(_ value: Any, forKey key: RemoteStorageKey) {
    defaults.set(value, forKey: key.rawValue)
  }
   
  func retrieve(forKey key: RemoteStorageKey) -> Any? {
    return defaults.object(forKey: key.rawValue)
  }
   
  func removeObject(forKey key: RemoteStorageKey) {
    defaults.removeObject(forKey: key.rawValue)
  }
   
  func resetStorageKeys() {
    for key in RemoteStorageKey.allCases {
      defaults.removeObject(forKey: key.rawValue)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@interface RemoteStorage ()
 
@property (nonatomic) StorageType storageType;
@property (nonatomic, strong) NSUserDefaults *defaults;
 
@end
 
@implementation RemoteStorage
 
- (id)initWithStorageType:(StorageType)storageType {
  if (self = [super init]) {
    self.storageType = storageType;
  }
  return self;
}
 
- (void)store:(id)value forKey:(RemoteStorageKey)key {
  [[self defaults] setValue:value forKey:[self rawValueForKey:key]];
}
 
- (id)retrieveForKey:(RemoteStorageKey)key {
  return [[self defaults] objectForKey:[self rawValueForKey:key]];
}
 
- (void)removeObjectForKey:(RemoteStorageKey)key {
  [[self defaults] removeObjectForKey:[self rawValueForKey:key]];
}
 
- (void)resetStorageKeys {
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingCustomEvents]];
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingCustomAttributes]];
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingUserAttributes]];
}
 
- (NSUserDefaults *)defaults {
  if (!_defaults) {
    switch (self.storageType) {
      case StorageTypeStandard:
        _defaults = [NSUserDefaults standardUserDefaults];
        break;
      case StorageTypeSuite:
        _defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.company.appname.xyz"];
        break;
    }
  }
  return _defaults;
}
 
- (NSString*)rawValueForKey:(RemoteStorageKey)remoteStorageKey {
    switch(remoteStorageKey) {
    case RemoteStorageKeyPendingCustomEvents:
      return @"pending_custom_events";
    case RemoteStorageKeyPendingCustomAttributes:
      return @"pending_custom_attributes";
    case RemoteStorageKeyPendingUserAttributes:
      return @"pending_user_attributes";
    default:
      [NSException raise:NSGenericException format:@"Unexpected FormatType."];
  }
}

UserAttribute 헬퍼 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum UserAttribute: Hashable {
  case email(String?)
}
 
// MARK: - Codable
extension UserAttribute: Codable {
  private enum CodingKeys: String, CodingKey {
    case email
  }
   
  func encode(to encoder: Encoder) throws {
    var values = encoder.container(keyedBy: CodingKeys.self)
     
    switch self {
    case .email(let email):
      try values.encodeIfPresent(email, forKey: .email)
    }
  }
   
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
     
    let email = try values.decodeIfPresent(String.self, forKey: .email)
    self = .email(email)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@implementation UserAttribute
 
- (id)initWithUserField:(NSString *)userField attributeType:(UserAttributeType)attributeType {
  if (self = [super init]) {
    self.userField = userField;
    self.attributeType = attributeType;
  }
  return self;
}
 
- (void)encodeWithCoder:(NSCoder *)encoder {
  [encoder encodeObject:self.userField forKey:@"userField"];
  [encoder encodeInteger:self.attributeType forKey:@"attributeType"];
}
 
- (id)initWithCoder:(NSCoder *)decoder {
  if (self = [super init]) {
    self.userField = [decoder decodeObjectForKey:@"userField"];
     
    NSInteger attributeRawValue = [decoder decodeIntegerForKey:@"attributeType"];
    self.attributeType = (UserAttributeType) attributeRawValue;
  }
  return self;
}
 
@end

EventName 사전 헬퍼 파일

1
2
3
4
5
6
7
8
9
10
11
12
extension Dictionary where Key == String, Value == Any {
  init(eventName: String, properties: [String: Any]? = nil) {
    self.init()
    self[PushNotificationKey.eventName.rawValue] = eventName
     
    if let properties = properties {
      for (key, value) in properties {
        self[key] = value
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implementation NSMutableDictionary (Helper)

+ (instancetype)dictionaryWithEventName:(NSString *)eventName
                              properties:(NSDictionary *)properties {
  NSMutableDictionary *dict = [NSMutableDictionary dictionary];
  dict[@"event_name"] = eventName;

  if (properties) {
    for (id key in properties) {
      dict[key] = properties[key];
    }
  }

  return dict;
}
 
@end

결과 분석

분석 카테고리에 맞는 보고 화면을 사용하세요:

커스텀 보고서 생성에 대해서는 보고서 빌더를 참조하세요.

관련 참조

New Stuff!