【iOS】広告の事前読み込みチャレンジ

はじめに

どうも。

ジモティーでiOSアプリ開発チームのマネージャーをやらせてもらっている、ていです。

前回の記事で弊社のiOSアプリにアーキテクチャを導入した過程の計画編をお届けしたので順番的には導入編をお届けするのが筋かと思いますが、気分が乗らないので少し前に広告の取得表示改善に取り組んだ話をしようと思います。

広告の課題

弊社の広告は所謂アドネットワークから配信される広告をSDKを通じてアプリに表示させるよくある手法を取ってるのですが、課題はたくさんあります。

私自身ジモティーに入社して7年目を迎えておりますが、この7年は広告(SDK)との戦いの歴史だと言っても過言ではないと自分では思ってます。

たくさんある課題の一つに広告の表示が遅く、ユーザさんが広告を目にする前にスクロールやページ遷移で広告表示画面から離れてしまうというものがありました。

そもそも見栄えが悪いのもありますし、弊社としても売上の機会損失につながっている状況です。

事前読み込み方式のチャレンジ

広告はリクエストを送った後、Biddingと呼ばれる入札が行われてその時点で収益効果の高い広告を返す仕組みになっている関係上リクエストからレスポンスまで一定の時間がかかってしまい、さらに(Admobの場合)カスタムイベントをたくさん設定しているとその分さらに時間がかかるということがあるので、どうしてもページ表示から広告表示までタイムラグが発生してしまいます。

弊社のアプリは特にそのタイムラグが大きく、レスポンスが返ってくるまでは広告表示枠が白板状態でぽっかりと空いてしまい非常に見栄えが悪い状態になっています。

そこで以下のような考え方で改修を行なってみることになりました。

1. 現在の広告取得後の表示タイミングで次に表示する予定の広告を取得しておいて、端末内に保存する

2. 次の広告表示タイミングでまず事前取得した広告を表示し、さらにその次に表示する予定の広告を取得しておいて端末内に保存する

3. こうしておけば初回リリース以降は(広告取得失敗がない限り)常にほぼ待ち時間0で広告を表示することが可能になる。

実際のソース(一部抜粋)

キャッシュクラス

class AdCacheHelper {
    private static var preloadAd: PreloadAd?

    static func getAdView() -> GADBannerView? {
        Self.checkAdGetTime()
        let localData = Self.preloadAd
        Self.removeAdView()
        return localData?.adView
    }

    static func setAdView(_ adView: GADBannerView) {
        Self.preloadAd = PreloadAd(createdAt: Date(), adView: adView)
    }

    // 取得から1時間経過した事前読み込み広告は破棄
    private static func checkAdGetTime() {
        if let preloadAd = self.preloadAd {
            let adLimitDate = Calendar.current.date(byAdding: .hour, value: 1, to: preloadAd.createdAt)!
            if adLimitDate < Date() {
                Self.removeAdView()
            }
        }
    }

    private static func removeAdView() {
        Self.preloadAd = nil
    }
}

struct PreloadAd {
    let createdAt: Date
    let adView: GADBannerView
}

呼び出しがわ

    public func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
        if let adCacheableViewController = self.rootViewController as? AdLoadable {
            switch self.loadStatus {
            // 事前取得表示済みの場合と再取得後はキャッシュ保存してリフレッシュ待機状態にする
            case .preLoaded, .reRequested:
                AdCacheHelper.setAdView(bannerView)
                self.loadStatus = .refreshing
            // 初回起動時 or 破棄されてる場合 or 異常として広告がない場合は取得した広告を表示して事前読み込み用の広告を再取得
            case .firstLoading:
                adCacheableViewController.loadAd(bannerView: bannerView, admobUnitId: self.admobUnitId)
                self.loadStatus = .reRequested
                self.requestAds()
            // リフレッシュはそのまま表示するのみ
            case .refreshing:
                adCacheableViewController.loadAd(bannerView: bannerView, admobUnitId: self.admobUnitId)
            }
        }
    }

※1 実装に関する指摘コメント歓迎です!

※2 ただしあまりに厳しい言葉だと実装者のメンタルに響きますのでお手柔らかにお願いします。

※3 実装に関してzaimさんのブログを勝手に参考にさせていただきました。

結果

結果としては見栄えの部分ではある程度改善されたのですが、数字上の効果はあまり改善が見られませんでした。

仮説としてはそもそも広告の取得が遅すぎて、事前読み込み完了前にページ遷移などで広告リクエストが破棄されてしまいいつまで経っても事前広告がキャッシュされないパターンが多いのでは?というものが有力そうです。

最後に

この経験を踏まえ今後は事前読み込み方式を一度に複数読み込んでおくとか、そもそもBiddingの速度を上げられないか?など多岐にわたる改善方法を検討して進めていく予定です。

広告を扱われていて速度改善で良い成果を挙げられた方などいらっしゃいましたら、是非お知恵をお貸しいただけると幸いです。

またはジョインしていただき一緒にジモティープロダクトを改善していただけるとよりハッピーなので、よろしくお願いします。


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです