iOSアプリ開発の逆引き辞典

iPhone/iPadで使えるアプリ開発のTipsをまとめてみました

サーバーに置いた定義ファイルに応じて表示させる広告を変更させる

AdWhirlは、2013年にGoogle Admobに買収され、Google AdMob 広告ネットワーク メディエーション(AdMob Ad Network Mediation)への移行期間を経て2013年9月30日にサービスを終了しました。

このようにスマートフォンアプリの広告関連の会社は興隆が活発におこなわれる分、他の会社に買収されてしまってサービスを辞めてしまったり形態を変更するということがあります。または広告SDKの仕様変更に伴い、ある日突然広告が表示されなくなる可能性もあります。更に不安なことを考えると、テレビや大手ニュースサイトなどで自分のアプリが紹介され、急激にインプレッション数が増加したせいで、不正利用と見なされてアカウントをBANされる可能性もあります。

頻繁にアップデートしている(メンテナンスしている)アプリの場合だと、バグ修正やパフォーマンス改善のついでに、広告SDKをバージョンアップさせたり、広告SDKの仕様変更に対応したり、広告配信サービスを終了する広告会社から別の広告会社に変更させたりということが比較的やりやすいでしょう。

しかし、個人で(極端な例ですが)アプリを100本リリースしている場合や、別案件で多忙で割く時間がない場合など、現実的に考えるとどうしても対応が遅れてしまいます。仮にすぐに広告の切り替え作業をしたとしても、iOSアプリの場合にはAppleの審査待ちというタイムラグがどうしても発生してしまいます。

これらの要因で対応が遅れていけばいくほど、広告が表示されていない時間が増えてしまいます。広告が表示されない時間が増えるということは、本来その間に発生していただろう収入が減ってしまうことになります。それらのリスクを考えていくと、特定の広告会社のサービスに依存しているというのはあまり得策ではないと言えます。

最初に名前を出した「AdWhirl」や「AdMob Mediation」は、サプライサイドプラットフォーム(Supply Side Platform、SSP)とも呼ばれ、各社の広告を管理することができます。例えば、なんらかの原因で広告を表示できなくなったり、クリック単価が下がった場合に回避する方法として、どのアドネットワークの広告をどれくらいの確率で表示させるかを設定して、アプリに反映させることができます。

AdMob Mediationは、AdMobのAd Network Mediation SDKからダウンロードして導入することが可能です。

サプライサイドプラットフォームが実施しているだろうことを、ここではあくまでも簡単にですが自前で実装することによって、懸念していたリスクを避けてみましょう。

どの広告を表示させるか定義する

最初にアプリをリリースする段階で複数の広告会社の広告SDKを組み込んでおき、どのSDKを使って広告を表示させるのかを定義したファイルをアクセス可能な外部サーバーに配置することによって、広告表示に使用するSDKを切り替える方法を紹介します。

どのSDKを使用するのかを定義したファイルを仮に「advertising_switcher.json」とします。これを便宜上「広告定義ファイル」と呼ぶ事にします。AppBank NetworkとGoogle AdMobを切り替えて表示させるように対応してみましょう。

AppBank NetworkのSDKを使って広告を表示する場合は、下記のように定義します。

{ "enable_advertising" : "appbank"}

Google AdMobのSDKを使って広告を表示する場合は、下記のように定義します。

{ "enable_advertising" : "admob"}

このjsonファイルをアプリからアクセス可能なサーバーに置いてください。コード内では、 http://example.com/advertising_switcher.json としています。配置したサーバーに応じて書き換えてください。

広告定義ファイルを取得して、定義値に応じて広告を表示させる

iOSアプリで、非同期的にJSONファイルをダウンロードしてパースする処理は、「JSONファイルを非同期ダウンロードしてJSONとしてパースする」に詳細を書いておりますので、そちらの方をご覧ください。

事前にInterface Builderでバナー広告の表示位置にUIViewを配置して、adViewに接続しておいてください。

//
//  SBViewController.m
//  NendAdSample
//
//  Created by WADA KENJI on 2013/11/07.
//  Copyright (c) 2013年 Softbuild. All rights reserved.
//

#import "SBViewController.h"
#import "NADView.h"

@interface SBViewController ()

@property (weak, nonatomic) IBOutlet UIView *adView;

@end

画面の突入時のviewDidLoadメソッドの中でrefreshAdsメソッドを実行します。refreshAdsメソッドでは、NSURLConnectionクラスを使って非同期的にJSONファイルをダウンロードします。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self refreshAds];
}

// 広告の表示を更新する
- (void)refreshAds
{
    NSURL* url = [NSURL URLWithString:@"http://example.com/advertising_switcher.json"];
    [NSURLConnection sendAsynchronousRequest:[NSMutableURLRequest requestWithURL:url]
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse* response, NSData* data, NSError* error) {
                               [self didFinishedDownload:response data:data error:error];
                           }];
}

広告定義ファイルのダウンロードが完了すると、didFinishedDownload:data:error:メソッドを実行します。

このメソッドの中では、ダウンロードした広告定義ファイルをJSONとしてパースして、キー enable_advertising の値を取得して、どの広告を表示するか決定します。

// 広告定義をダウンロードして、定義に応じた広告を表示させる
- (void)didFinishedDownload:(NSURLResponse *)response 
    data:(NSData *)data error:(NSError *)error
{
    if (error) {
        // 広告定義の取得に失敗したので広告を非表示にする
        self.adView.hidden = YES;
        return;
    }
    
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    if ([httpResponse statusCode] != 200) {
        // 広告定義のダウンロードに失敗したので広告を非表示にする
        self.adView.hidden = YES;
        return;
    }
    
    // ダウンロードしたJSONデータのパースする
    NSError* perseError = nil;
    id json = [NSJSONSerialization JSONObjectWithData:data
        options:kNilOptions error:&perseError];
    if (perseError) {
        // 広告定義のパースに失敗したので広告を非表示にする
        self.adView.hidden = YES;
        return;
    }
    
    // 既に広告が表示されていれば、それを取得してSuperViewから削除する
    UIView *view = [self.adView viewWithTag:19821012];
    [view removeFromSuperview];
    
    // JSONファイルから定義を取り出す
    NSString *adtype = [json objectForKey:@"enable_advertising"];
    if ([adtype isEqualToString:@"appbank"]) {
        
        // AppBank Networkの広告を表示させる
        [self showNadView];
        
    } else if ([adtype isEqualToString:@"admob"]) {
        
        // Google AdMobの広告を表示させる
        [self showAdMobView];
        
    } else {
        // 想定外の広告定義値だったので広告を非表示にする
        self.adView.hidden = YES;
    }
}

もし、enable_advertising の値が、appbankの場合は、showNadViewメソッドを実行します。

- (void)showNadView
{
    CGRect f = CGRectZero;
    f.size.width = NAD_ADVIEW_SIZE_320x50.width;
    f.size.height = NAD_ADVIEW_SIZE_320x50.height;
    
    NADView *nendView = [[NADView alloc] initWithFrame:f];
    [nendView setTag:19821012];
    [nendView setNendID:@"XXXXXXXXXXXXXXX" spotID:@"XXXXXXX"];
    [nendView setDelegate:self];
    [nendView load:nil];
    [self.adView addSubview:nendView];
}

もし、enable_advertising の値が、admobの場合は、showNadViewメソッドを実行します。

- (void)showAdMobView
{
    GADBannerView *adMobView = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];
    [adMobView setTag:19821012];
    [adMobView setAdUnitID:@"ca-app-pub-XXXXXXXXXXXXXXX/XXXXX"];
    [adMobView setRootViewController:self];
    [adMobView setDelegate:self];
    [adMobView loadRequest:[GADRequest request]];
    [self.adView addSubview:adMobView];
}

広告の取得に失敗した時の処理

それぞれのデリゲートの中では、以下のように広告の取得に失敗した場合には、広告表示の土台となる adView のhiddenプロパティにYESを設定して、adViewごと表示されないようにしています。

#pragma mark - AppBank Network SDKのデリゲート

- (void)nadViewDidFinishLoad:(NADView *)adView
{
    // 広告の表示に成功したのでadViewを表示させる
    self.adView.hidden = NO;
}

- (void)nadViewDidFailToReceiveAd:(NADView *)adView
{
    // 広告定義のパースに失敗したのでadViewを非表示にする
    self.adView.hidden = YES;
}

#pragma mark - Nend AppBank Network SDKのデリゲート

-(void)adViewDidReceiveAd:(GADBannerView *)view
{
    // 広告の表示に成功したのでadViewを表示させる
    self.adView.hidden = NO;
}

-(void)adView:(GADBannerView *)view 
    didFailToReceiveAdWithError:(GADRequestError *)error
{
    // 広告定義のパースに失敗したのでadViewを非表示にする
    self.adView.hidden = YES;
}

ここでは簡単にadViewを隠しているだけですが、広告が取得できなかった(インターネットに接続されていない)場合などには、アプリ内リソースを表示させたりすると良いかもしれません。

以上で、広告定義ファイルから使用する広告を決定するという対応ができました。広告が表示されないというリスクを完全に排除できるわけではありませんが、例えば広告会社Aの広告が表示できなくなってしまった時に原因解決まで、広告会社Bの広告を表示させるという時間稼ぎができるようになりました。

上記のコード(refreshAdsメソッド)では、広告を表示させようとする度に http://example.com/advertising_switcher.json にアクセスしています。毎回どの広告を表示させるのかをダウンロードして判定するようになっていますが、外部サーバーから広告定義ファイルを取得する時間がどうしてかかってしまいます。

広告定義ファイルの取得のタイミングを、アプリの起動時に限定させたり(バックグランドからフォアグランドへの切り替えでは取得しにいかない)、定義ファイルを一定の期間保持するなど、キャッシュするなどして対策すると良いでしょう。