【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

この記事からわかること

  • Swift/Firebase作成したiOSアプリA/B Testing導入する方法
  • Firebaseインストール認証トークン取得方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Firebaseの概要や登録方法については下記記事を参考にしてください。

A/Bテストとは?

A/Bテスト」とは2種類以上のパターンをユーザーにランダムに表示させ表示させ、一番効果的なパターンを測定するためのテスト手法です。例えばボタンの色味を「赤」と「青」を用意してどちらの方がクリック率が高かったかなどを測定することでよりユーザーに対してよりリーチできている方を判別するために活用されます。

測定例

Firebase A/B Testing

公式リファレンス:Firebase A/B テストについて

Firebaseでは簡単にA/Bテストを実装・解析することができる「A/B Testing」が用意されています。内部的には「Firebase Remote Config」と「Firebase Analytics」を組み合わせて「A/Bテスト」機能の実装と解析が行えるようになっています。

Firebase A/B Testingの設定と実装方法

Firebase A/B Testingを実際に使用してA/Bテストを計測する方法を見ていきます。まずはFirebase Console側でA/B Testingを有効にします。「A/B Testing」は左メニューの「実行」の中にあります。

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

テストを作成」をクリックするとサービス方法を選択できるので「Remote Config」を選択します。

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

最初にテスト名を入力します。今回は例として「ボタンデザインの分岐によるボタンクリック率を計測する」と仮定して進めていきます。

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

続いて対象のアプリを選択し、対象のアプリのアクティブユーザーに対して何%にテストを実行するかを設定します。100%を指定すれば全ユーザーに対してテストが試行されます。

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

続いて紐付けたい「Firebase Analytics」のイベントを指定します。既存で登録しているイベントもしくは新規で作成することができるので新規で作成したい場合はイベント名を入力して「イベント「XXXXX」を作成」をクリックします。

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

最後に「Firebase Remote Config」のキーと値を指定します。こちらも既存で設定済みのものもしくは新規で作成することが可能です。ここで複数の値を追加すればA/Bの2パターンだけでなく複数パターンでテストを行うことはバリアントの重み付け

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認

設定できたら「確認」をおして作成完了しておきます。この段階ではまだ公開はされていないので「テスト開始」を押して公開してあげればテストが開始します。

iOSアプリ側の実装

iOSアプリ側では「Remote ConfigによるUIの分岐」と「Analyticsのイベント送信」を実装します。まずはRemoteConfigから値をフェッチできるように以下のようなRemoteConfigManagerクラスを定義しました。ここでは単純にRemote Configから流れてくる値をストリームとして公開しています。

import Combine
@preconcurrency  import FirebaseRemoteConfig

final class RemoteConfigManager: Sendable {
    private let remoteConfig: RemoteConfig
    
    @MainActor
    var buttonType: AnyPublisher<Int, Never> {
        _buttonType.eraseToAnyPublisher()
    }

    @MainActor
    private var _buttonType = CurrentValueSubject<Int, Never>(1)

    init() {
        // RemoteConfigインスタンスを取得
        remoteConfig = RemoteConfig.remoteConfig()
        let settings = RemoteConfigSettings()
        // 最小フェッチ間隔 開発環境では0(制限なし)を指定
        settings.minimumFetchInterval = 0
        // 設定を反映
        remoteConfig.configSettings = settings
    }

    func initialize() {
        fetchRemoteConfig()
    }

    /// リモートサーバーから最新の設定値を取得してremoteConfigインスタンスに反映
    private func fetchRemoteConfig() {
        remoteConfig.fetch { [weak self] status, error in
            guard let self else { return }
            if status == .success {
                self.remoteConfig.activate(completion: nil)
                Task {
                    await applyButtonType()
                }
            } else {
                #if DEBUG
                    AppLogger.logger.debug("Error: \(error?.localizedDescription ?? "No error available.")")
                #endif
            }
        }
    }

    /// `BUTTON_TYPE`設定値を取得し公開
    private func applyButtonType() async {
        let version = Int(truncating: remoteConfig[RemoteConfigManager.BUTTON_TYPE].numberValue)
        await MainActor.run {
            _buttonType.send(version)
        }
    }

    private static let BUTTON_TYPE = "button_type"
    
}

これをViewModel層ではRemote Configの値によりUIを分岐するためのフラグを公開します。


import Foundation
import Combine

@MainActor
final class ABTestViewModel: ObservableObject {
    @Published var buttonType: Int = 1

    private let remoteConfigManager: RemoteConfigManager
    private var cancellables = Set<AnyCancellable>()
      
    init(remoteConfigManager: RemoteConfigManager = RemoteConfigManager()) {
        self.remoteConfigManager = remoteConfigManager
        bind()
        remoteConfigManager.initialize()
    }

    private func bind() {
        remoteConfigManager.buttonType
            .receive(on: DispatchQueue.main)
            .sink { [weak self] value in
                self?.buttonType = value
            }
            .store(in: &cancellables)
    }
}

View層でViewModelの値に応じてUIを分岐させるように実装します。そしてそれぞれのボタンタップイベントにAnalytics.logEventでログイベントを送信するようにしておきます。


struct ABTestView: View {
    @StateObject private var viewModel = ABTestViewModel()

    var body: some View {
        VStack(spacing: 20) {
            // A/Bテスト対象のUI部分
            if viewModel.buttonType == 1 {
                Button {
                    print("Aボタンが押されました")
                    Analytics.logEvent("test_button_click_event", parameters: nil)
                } label: {
                    Text("タイプAボタン")
                }.buttonStyle(.borderedProminent)
                  .tint(.blue)
            } else {
                Button {
                    print("Bボタンが押されました")
                    Analytics.logEvent("test_button_click_event", parameters: nil)
                } label: {
                    HStack {
                        Image(systemName: "star.fill")
                        Text("タイプBボタン")
                    }
                }.buttonStyle(.bordered)
                  .tint(.orange)
            }
        }.padding()
    }
}

任意のデバイスでテスト検証する

任意のデバイスで意図的にパターン(バリアント)を指定して検証するためにはテスト検証するには「テストデバイス管理」を選択します。ここにデバイス単位で取得できる「Firebaseインストール認証トークン」または「FCMトークン」を指定することで特定のパターンを対象のデバイスに表示させることができます。「Firebaseインストール認証トークン」は以下のようなコードで取得することが可能です。

【Swift/iOS/Firebase】A/B Testingの実装方法!テスト結果の確認
import FirebaseInstallations
Task {
    do {
      let result = try await Installations.installations()
        .authTokenForcingRefresh(true)
      print("Installation auth token: \(result.authToken)")
    } catch {
      print("Error fetching token: \(error)")
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index