LoginSignup
5
7

More than 5 years have passed since last update.

Google Analytics SDK for iOSのイベントトラッキング用に型を作って制約を与える

Last updated at Posted at 2017-04-25

はじめに

最近、Google アナリティクス SDKでイベントトラッキングを行う際に考えたことを共有しておきます。ちなみに、タイトルにある「型を作って制約を与える」というのはstruct, enum, protocolを利用していることを指します。

要点

  • イベントトラッキング用のパラメータを整理したい
  • イベントトラッキングの仕様にない組み合わせはできないようにしたい
  • ソースコードが増えても気にしない

Google アナリティクス SDKのイベントトラッキングについて

前提としてSDKでイベントをサーバに送信する項目は4つ

  • カテゴリ
  • アクション
  • ラベル
  • バリュー

実際のアプリ開発では下記のようなスプレッドシートが作られることが多いと思います。

カテゴリ アクション ラベル バリュー
アップロード開始 Upload Started <開始時間> アップロード数
アップロード終了 Upload End <終了時間> アップロード数
アップロード成功 Upload Succeeded VideoもしくはPhoto
アップロード失敗 Upload Failed VideoもしくはPhoto
アプリ起動 AppStatus Launched nil もしくは RemoteNotification もしくは LocalNotification
アプリがSuspend AppStatus Suspended nil

この表では、パターンは大本で2つに別れていて、それがカテゴリUploadとAppStatusになってるというのが分かるでしょうか。

そうして、やりたいことというのはカテゴリがUploadのときにはアクションがStartedかSuccededのみになるようにしたい。逆に言うとカテゴリがUploadのときにはLaunchedにならないし、Suspendedにならないようにしたいというのがこの記事でやりたいことです。

違う言い方をすると、表にあるパターン以外は組めないようにしたい。

どうやるか

まあやり方はいっぱいあるでしょうよ、ということでまずやりたくないパターンを先に書く。

やりたくないパターン

まず次のようにはしたくないわけです。アクションは列挙しているけど利用する際に関係のないアクションについて考えないといけなくなる。

// 次のようなenumにはしたくない
enum Action {
    case started    // Uploadのカテゴリでしか使わない
    case end        // Uploadのカテゴリでしか使わない
    case succeeded  // Uploadのカテゴリでしか使わない
    case launched   // AppStatusのカテゴリでしか使わない
    case suspended // AppStatusのカテゴリでしか使わない
}

// これならまあ良さそうだけど
trackEvent(category: .upload, action: .started)

// しかし次のようにUploadのときはLaunchedに出来てしまう。動かしてから気づく
trackEvent(category: .upload, action: .launched)

UploadカテゴリでLaunchedアクションにできるのは避けたいわけです。

やりたいパターン

まずprotocol

ということで、本題に近づくため、まずはprotocolを定義してメソッドの引数に渡せるようにする

protocol AnalyticsEvent {
    var category: String { get }
    var action: String { get }
    var label: String? { get }
    var value: String? { get }
}

このプロトコルにそっていれば次のようなメソッドの引数に渡せるようにしたいだけ

// イベントトラッキングメソッド
func track(_ event: AnalyticsEvent) 

パターンをstructで用意する

まず、表にあったカテゴリUploadのパターンのみを実現するstruct ContentUploadEventを用意する

struct ContentUploadEvent: AnalyticsEvent {

    enum ActionAndLabel {
        case started(CustomLabel)
        case end(CustomLabel)
        case succeeded(ContentTypeLabel)

        struct Actions {
            static let started = "Started"
            static let end = "End"
            static let succeeded = "Succeeded"
        }

        var rawValues: (action: String, label: String) {
            switch self {
            case .started(let label):
                return (action: Actions.started, label: label.name)
            case .end(let label):
                return (action: Actions.end, label: label.name)
            case .succeeded(let label):
                return (action: Actions.succeeded, label: label.rawValue)
            }
        }
    }

    struct CustomLabel {
        let name: String
    }

    enum ContentTypeLabel: String {
        case video = "Video"
        case photo = "Photo"
    }

    let actionAndLabel: ActionAndLabel

    let category = "Upload"
    var action: String {
        return actionAndLabel.rawValues.action
    }

    var label: String? {
        return actionAndLabel.rawValues.label
    }

    var value: String?
}

ContentUploadEventの利用例は次のような感じ。

let event1_1 = ContentUploadEvent(actionAndLabel: .started(ContentUploadEvent.CustomLabel(name: "開始時間的な文字列")), value: "1")
let event1_2 = ContentUploadEvent(actionAndLabel: .end(ContentUploadEvent.CustomLabel(name: "終了時間的な文字列")), value: "1")

let event2_1 = ContentUploadEvent(actionAndLabel: .succeeded(.video), value: nil)
let event2_2 = ContentUploadEvent(actionAndLabel: .succeeded(.photo), value: nil)

とにかくやりたいのはUploadのカテゴリではそのカテゴリ内のアクションだけを使えるようにしている。

残り、表にあったカテゴリAppStatusのパターンのみを実現するstruct AppStatusEventを用意する

struct AppStatusEvent: AnalyticsEvent {

    enum ActionAndLabel {
        case launched(NotificationLabel?)
        case suspended

        struct Actions {
            static let launched = "Launched"
            static let suspended = "Suspended"
        }

        var rawValues: (action: String, label: String?) {
            switch self {
            case .launched(let label):
                return (action: Actions.launched, label: label?.rawValue)
            case .suspended:
                return (action: Actions.suspended, label: nil)
            }
        }

        enum NotificationLabel: String {
            case remote = "RemoteNotification"
            case local = "LocalNotification"
        }
    }

    let category = "AppStatus"
    let actionAndLabel: ActionAndLabel

    var action: String {
        return actionAndLabel.rawValues.action
    }

    var label: String? {
        return actionAndLabel.rawValues.label
    }
    var value: String?
}

利用例は

let event3_1 = AppStatusEvent(actionAndLabel: .launched(nil), value: nil)
let event3_2 = AppStatusEvent(actionAndLabel: .launched(.remote), value: nil)
let event3_3 = AppStatusEvent(actionAndLabel: .launched(.local), value: nil)
let event3_4 = AppStatusEvent(actionAndLabel: .suspended, value: nil)

これで各種のイベントはパターンにそっていてイベントを作ったらそれをメソッドに投げるだけ

おわりに

他に良い方法がありそうだけど、思いついたら教えて欲しいです

5
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7