ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS/Swift] Firebase A/B Test
    야매 iOS 2022. 11. 19. 19:05

    https://www.google.com/url?sa=i&url=https%3A%2F%2Fproductcoalition.com%2Fare-you-segmenting-your-a-b-test-results-c5512c6def65&psig=AOvVaw2p149FDuNn7dv85-Sq3XtU&ust=1668937149332000&source=images&cd=vfe&ved=0CBAQjRxqFwoTCNjqrMH5ufsCFQAAAAAdAAAAABAM

    AB 테스트를 하기 위해선 먼저 가설이 필요하다

    어떤 변화가 일어났을 때 해당 변화가 실제로 유효한지 그리고 가설에 맞는지 검증해야 될 필요가 있다

     

    그래서 그룹을 나누고 그 그룹들에게 다른 화면을 보여줘서 그에 대한 결과를 얻어낼 수 있다

    그룹에 많은 사용자가 있다면 더욱 더 유효한 데이터를 얻을 수 있다

    - 외부 요인으로 발생된 이벤트인지 아니면 변경사항으로 발생된 이벤트인지

    AB Test

    Firebase Analytics는 이벤트 베이스 모델을 사용한다

    - 유저가 어떤 동작을 하면 firebase Analytics가 이벤트를 서버에게 보낸다

    - 서버는 가공한 이벤트를 바탕으로 콘솔에 그래프로 보여준다

    여기서 Remote Config를 사용해 AB 테스트를 진행할 것이기 때문에 원격 구성을 체크한다

    기본사항

    기본사항에는 이름과 설명을 적어주면 된다

     

    타겟팅

    타겟팅은 앱 별로 따로 진행 한다

    • 플랫폼마다 유저의 행동이 다를 수 있기 때문
    • 그 아래 여러 추가적인 조건을 정할 수 있다 (ex/ 언어) 

    이 실험에 참가 시킬 유저들의 비율을 정해야 한다. 많은 사람들이 참여한다면 더욱 신뢰적인 데이터를 얻게 된다. 하지만 시도하려는게 지나치게 도전적이면 작은 그룹으로 테스트를 하는 것이 나을 수도 있다

     

    목표

    이 실험을 하는 목적 또는 어떤 것을 극대화 하고 싶은지 (ex/ 구독)

    변행

    각각의 그룹에 맞는 Remote Config 값을 설정해준다

    각각의 그룹에서 가끔 빈문자열을 넣기도 하는데 그것은 사용자가 실험에 참여하지 않을 때의 값을 보여달라는 뜻

    현재 위에서 작성된 AB 테스트가 제대로 동작하는지 확인하기 위해서 해당 테스트에 테스트 기기를 등록할 수 있다

    테스트 기기를 등록하기 위해선 Firebase 설치 인증 토큰이 필요한데 아래의 코드를 통해 얻을 수 있다.

    그리고 해당 테스트 기기를 어떤 그룹에 배정할 것인지도 설정할 수 있다.

    Installations.installations().authToken { result, _ in
    	print("Fiebase instance ID token is \(result?.authToken ?? "n/a")")
    }

    A/B  테스트 코드

    아래의 코드에서 2가지 테스트를 진행하고 있다

    1. 버턴의 모양

    2. 버튼을 눌렀을 때 어떤 화면으로 넘어갈 것인지

    AppDelegate

    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Override point for customization after application launch.
            FirebaseApp.configure()
    
            Installations.installations().authToken { result, _ in
              print("Fiebase instance ID token is \(result?.authToken ?? "n/a")")
            }
    
            return true
        }
    }

     

    RCValue

    import UIKit
    import Firebase
    
    enum ValueKey: String {
        case buttonTitle
        case buttonCornerRadius
        case buttonBackgroundColor
        case buttonTriggeredViewController
    }
    
    final class RCValues {
        static let sharedInstance = RCValues()
    
        var loadingDoneCallback: (() -> Void)?
        var fetchComplete: Bool?
    
        private init() {
            loadDefaultValue()
            fetchCloudValues()
        }
    
        private func loadDefaultValue() {
            let appDefaults: [String: Any?] = [
                ValueKey.buttonTitle.rawValue: "Go To Submit",
                ValueKey.buttonCornerRadius.rawValue: "0.0",
                ValueKey.buttonBackgroundColor.rawValue: "#126424",
                ValueKey.buttonTriggeredViewController.rawValue: "ASubmitViewController"
            ]
    
            RemoteConfig.remoteConfig().setDefaults(appDefaults as? [String: NSObject])
        }
    
        private func fetchCloudValues() {
            #if DEBUG
            activateDebugMode()
            #endif
    
            RemoteConfig.remoteConfig().fetch { [weak self] _, error in
                if let error = error {
                    print("error fetching remote values \(error)")
                    return
                }
    
                RemoteConfig.remoteConfig().activate { [weak self] _, _ in
                    print("remote value fetched")
                    self?.fetchComplete = true
                    self?.loadingDoneCallback?()
                }
            }
        }
    
        private func activateDebugMode() {
            let settings = RemoteConfigSettings()
            settings.minimumFetchInterval = 0
            RemoteConfig.remoteConfig().configSettings = settings
        }
    
        func string(forKey key: ValueKey) -> String {
          RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? ""
        }
    
        func double(forKey key: ValueKey) -> Double {
          RemoteConfig.remoteConfig()[key.rawValue].numberValue.doubleValue
        }
    
        func color(forKey key: ValueKey) -> UIColor {
          let colorAsHexString = RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? "#FFFFFFFF"
          let convertedColor = UIColor(colorAsHexString)
          return convertedColor
        }
    }

    // ViewController

    import UIKit
    
    import Firebase
    import SnapKit
    import Then
    
    class ViewController: UIViewController {
    
        private lazy var button = UIButton().then {
            $0.setTitle("Go To Submit", for: .normal)
            $0.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
            $0.setTitleColor(.black, for: .normal)
            $0.clipsToBounds = true
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            render()
            // Do any additional setup after loading the view.
        }
    
        private func render() {
            button.layer.cornerRadius = RCValues.sharedInstance.double(forKey: .buttonCornerRadius)
            button.setTitle(RCValues.sharedInstance.string(forKey: .buttonTitle), for: .normal)
            button.backgroundColor = RCValues.sharedInstance.color(forKey: .buttonBackgroundColor)
    
            view.addSubview(button)
            view.backgroundColor = .white
    
            button.snp.makeConstraints {
                $0.center.equalToSuperview()
            }
        }
    
        @objc
        func buttonPressed() {
            Analytics.logEvent(
                "GoToSubmitButtonPressedexch",
                parameters: nil
            )
    
            switch RCValues.sharedInstance.string(forKey: .buttonTriggeredViewController) {
            case "ASubmitViewController":
                let vc = ASubmitViewController()
                vc.delegate = self
                self.present(vc, animated: true)
            default:
                self.navigationController?.pushViewController(BSubmitViewController(), animated: true)
            }
    
        }
    }
    extension ViewController: ASubmitViewControllerButtonProtocol {
        func touchedSubmitButton() {
            self.dismiss(animated: true)
            self.navigationController?.pushViewController(SubmitResultViewController(), animated: true)
        }
    
    
    }

Designed by Tistory.