Skip to content

プッシュ分析とカスタムイベントのログ記録

このページでは、ネイティブプッシュ分析(開封、影響を受けた開封、キャンペーンレポート)とプッシュペイロードからのカスタムデータのログ記録(カスタムイベントと属性)のワークフローについて説明します。このガイドを使用して、ユースケースに該当するワークフローを特定し、プラットフォームに応じた手順に従ってください。

前提条件

開始する前に、プラットフォームの初期プッシュ通知統合を完了してください:

ネイティブプッシュ分析とカスタムイベントのログ記録の違い

以下のワークフローはそれぞれ異なるレポート画面を持っています。

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を参照してください。

Web プッシュの場合、Web プッシュ通知の説明に従って、サービスワーカーと SDK の初期化を設定してください。

その他のコードサンプルについては、Braze Web SDK リポジトリを参照してください。

プッシュペイロードからカスタムデータをログ記録する

プッシュペイロードのキーと値のペアから、ビジネスロジックに紐づくカスタムイベントや属性などの追加データをログ記録する必要がある場合は、このセクションを使用してください。

カスタムイベントの詳細については、カスタムイベントを参照してください。SDK メソッドを通じてカスタムイベントをログ記録するには、カスタムイベントのログ記録を参照してください。

オプション A:/users/trackエンドポイントでログ記録する

/users/trackエンドポイントを呼び出すことで、リアルタイムに分析をログ記録できます。

ユーザープロファイルを識別するには、プッシュペイロードのキーと値のペアにbraze_idを含めてください。

オプション B:アプリ起動後に SDK メソッドでログ記録する

ペイロードデータをローカルに保存し、アプリの初期化後に SDK メソッドを通じてカスタムイベントと属性をログ記録することもできます。このアプローチは、分析データを先に永続化し、次回のアプリ起動時にフラッシュする通知コンテンツエクステンションのフローで一般的です。

通知コンテンツエクステンションからのログ記録(Swift)

以下の手順では、Swift 通知コンテンツエクステンションからカスタムイベント、カスタム属性、ユーザー属性を保存・送信する方法を説明します。

ステップ 1:Xcode で App Groups を設定する

Xcode で、メインアプリターゲットにApp Groups機能を追加します。App Groups をオンにし、+ をクリックして新しいグループを追加します。アプリのバンドル ID を使用してグループ識別子を作成します(例:group.com.company.appname.xyz)。メインアプリターゲットとコンテンツエクステンションターゲットの両方で App Groups をオンにしてください。

メインアプリと通知エクステンションで App Groups 機能が有効になっている Xcode の画面

ステップ 2:ログ記録する内容を選択する

スニペットを実装する前に、ログ記録したい分析カテゴリを選択してください:

  • カスタムイベント: ユーザーが行うアクション(例:フローの完了や特定の UI 要素のタップ)。カスタムイベントは、アクションベースのトリガー、セグメンテーション、イベント分析に使用します。詳細については、カスタムイベントカスタムイベントのログ記録を参照してください。
  • カスタム属性: 定義するプロファイルフィールド(例:plan_tierpreferred_language)で、時間の経過とともに更新されます。詳細については、カスタム属性ユーザー属性の設定を参照してください。
  • ユーザー属性: 標準プロファイルフィールド(例:メール、名、電話番号)。サンプルコードでは、型付きUserAttributeモデルで表現され、Braze のユーザーフィールドにマッピングされます。

このセクションのヘルパーファイル(RemoteStorageUserAttributeEventName 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]];
  }
}

ユーザー属性の保存

ユーザー属性を保存する際は、更新されるユーザーフィールド(emailfirst_namephone_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!