読者です 読者をやめる 読者になる 読者になる

アプリの離脱率を劇的に下げるために!iOSアプリの離脱ページの計測テクニック

ウェブサービスに限らず、アプリの継続率を上げるためには離脱率や離脱ページの計測は重要です。 離脱率の高いページを知り、改善することでアプリの継続率やコンバージョン率の向上につなげることができます。 特に新規ユーザーの離脱ページを改善すると継続率への影響は大きいでしょう。 今回は、iOSアプリの離脱ページ計測について3点にわけて紹介します。

  1. iOSアプリにおける離脱とは

  2. 最前面のUIViewの特定

  3. 最前面のViewController (離脱ページ) の特定 graph

iOSアプリにおける離脱とは

iOSアプリにける離脱とは、ホームボタンを押したり、かかってきた電話に出たりなど、そのアプリケーションの利用が終了することと同義です。 このタイミングは UIApplicationDelegate の applicationWillResignActive: というデリゲートメソッドで検知できます。 このタイミングで最終的に表示していたページ(=ViewController)を取得できればよいのですが、アプリケーションの実装によっては同時に複数のViewControllerが存在するため特定が困難です。 ここでは applicationWillResignActive: が呼ばれた時点で最前面にあるViewControllerを離脱ページとして計測すればよいでしょう。 最前面のViewControllerを取得するには、下記の2ステップです。

最前面のUIViewを特定 そのUIViewが属するUIViewControllerを特定
Google Analyticsなんかでもやってることでしょ?

Google Analyticsでも画面ごとの離脱率を計測することができます。 ですが、今回は新規ユーザーについてのみ、どの画面で離脱したかを計測するために実施しました。

最前面のUIViewの特定

最前面のUIViewを取得するには UIWindow から始まるsubviewsツリーの最後のViewを再帰的にたどって行くことで最前面のUIViewが取得できます。 最終的に applicationWillResignActive: で取得するので、UIApplicationDelegateのクラスでメソッドを実装します。

アプリケーションの最前面のUIViewを取得する処理の実装例

@implementation MyAppDelegate

- (UIView *)foregroundView { // UIWindowから始まるView階層を再帰的にたどる

    UIView *foregroundView = self.window;
    while (1) {
        UIView *tmpView = nil; 
        for (UIView *subview in foregroundView.subviews.reverseObjectEnumerator) { // 非表示のviewは除く
            if (!subview.hidden && subview.alpha > 0.f) {
                tmpView = subview; 
                break;
            }
        }
        if (tmpView) {
            foregroundView = tmpView;
        } else {
            break;
        }
    }
    return foregroundView;
}
@end 

最前面のViewController (離脱ページ) の特定

さきほど取得した最前面のUIViewのレスポンダチェーンをたどって行くと、そのUIViewが属するUIViewControllerが取得できます。 UIResponderクラスのカテゴリのメソッドを実装します。

UIViewのレスポンダチェーンから自身が所属するUIViewControllerを取得する処理の実装例

@implementation UIResponder (Addition)

- (UIViewController *)belongingViewController {
    UIResponder *responder = self;
    while (1) {
        if (!responder) {
            return nil;
        } else if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)responder;
        } else {
            responder = responder.nextResponder;
        }
   }
}
@end 

最終的に最前面のUIViewControllerを取得して計測する処理は下記のようになります。

最前面のUIViewControllerを取得して計測する処理の実装例

@implementationMyAppDelegate

- (UIViewController *)foregroundViewController { // 表示されている最前面のViewControllerを返す
    UIViewController *vc = self.foregroundView.belongingViewController;
    if ([vc isKindOfClass:[UINavigationController class]]) {
        // UINaviagtionControllerの場合はtopViewControllerを返す
        return ((UINavigationController *)vc).topViewController;
    } else {
        return vc;
    }
}

- (void)applicationWillResignActive:(UIApplication *)application { // 会員登録から24時間以内のユーザーのみログを落とす
    if ([[NSDate date] timeIntervalSinceDate:USER.createTime] <= 60 * 60 * 24) {
        UIViewContrller *vc = self.foregroundViewController; // ロギング処理
    }
}
@end

再帰処理をたくさんやってるけどパフォーマンスは?

計測処理が遅くてアプリケーションの使い勝手が悪くなるような事態は避けなければなりません。 今回は再帰処理を使っていますが、実機で確認してみたところ、1msecかからず返ってきました。 subviewsのツリーとレスポンダチェーンをたどるだけの処理なので、大した負荷はないようです。

まとめ

離脱率を計測するために、アプリケーション終了時に最前面のViewControllerを取得する処理を紹介しました。 レスポンダチェーンをたどることで、UIViewのサブクラスからView自身が所属するViewControllerを取得することもできますが、ViewがViewControllerの存在を意識する必要はないのでおすすめしません。 計測をすることはサービス改善の第一歩です。計測結果を解析してサービス改善に役立ててください。

最後に

今回の記事に物申す!という方はこちらまで↓ 【iOS】Appleも認めたiQONにさらに磨きをかけるエンジニア募集