iOS7.1でのiBeacon

(※2014/04/22 11時に加筆修正しました)

本書で紹介しているiBeaconの挙動がiOS7.1で変更になりました。特に「アプリが起動していなくてもビーコンを検知できる」という点が非常に便利になっています。

そこでiOS7.1でサンプルアプリを作ってiBeaconの挙動をいくつか試してみました。テストに用いたApplicationDelegateソースコードは次のようなものです。


■サンプルソースコード

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  _locationManager = [[CLLocationManager alloc] init];
  _locationManager.delegate = self;

  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  if(![defaults boolForKey:@"region001_monitoringStarted"]) {

   NSUUID* uuid = [[NSUUID alloc] initWithUUIDString:@”U-U-I-D-S-t-r-i-n-g”];
   CLBeaconRegion *region;
   region = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
                    major:1 minor:1 identifier:@”region001″];
   [_locationManager startMonitoringForRegion:region];
   // [_locationManager startRangingBeaconsInRegion:region];

  [defaults setBool:YES forKey:@"region001_monitoringStarted"];
  [defaults synchronize];
  }
  return YES;
}

- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
  if(state == CLRegionStateInside) {
    NSLog(@”locationManager didDetermineState INSIDE for %@”, region.identifier);
  }
  else if(state == CLRegionStateOutside) {
    NSLog(@”locationManager didDetermineState OUTSIDE for %@”, region.identifier);
  }
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
  NSLog(@”locationManager didEnterRegion for %@”, region.identifier);
}

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
  NSLog(@”locationManager didExitRegion for %@”, region.identifier);
}

- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
  NSLog(@”locationManager didRangeBeacons”);
}


■説明用語

以下では、iOSデバイスの状態を次のような用語で説明しています。

 アプリの状態
 ・サスペンド状態:一度アプリを起動し、ホームボタンを押して閉じた状態
 ・アプリ終了状態:ホームボタンを2度押しし、アプリをスワイプして消した状態
 ・アプリ起動状態:アプリを動作させている状態

 画面の状態
 ・ブラックアウト :画面が真っ黒で何も表示されていない状態。省電力モード
 ・ロックスクリーン:ブラックアウトからホームボタンを押した状態
 ・ホームスクリーン:アプリアイコンが並んでいる画面

■ビーコン検知の動作

<アプリ起動状態>

・ビーコンを検知すると
  ・locationManager:didEnterRegionが呼ばれる
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる
・ビーコンを見失うと
  ・見失ってから30〜45秒ほどのディレイ
  ・locationManager:didExitRegionが呼ばれる
  ・CLRegionStateOutsideで、locationManager:didDetermineStateが呼ばれる

<サスペンド状態、ホームスクリーン>
 ・アプリ起動状態と同じ動作

<サスペンド状態、ブラックアウト>
 ・アプリ起動状態と同じ動作

<アプリ終了状態、ホームスクリーン>
・ビーコンを検知すると
  ・対応するアプリがバックグラウンドで起動
  ・applicationDidFinishLaunchingが呼ばれる
  ・locationManager:didEnterRegionが呼ばれる
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる
・ビーコンを見失うと
  ・見失ってから30〜45秒ほどのディレイ
  ・locationManager:didExitRegionが呼ばれる
  ・CLRegionStateOutsideで、locationManager:didDetermineStateが呼ばれる

<アプリ終了状態、ブラックアウト>
 ・アプリ終了状態、ホームスクリーンと同じ動作

<iOSデバイス再起動直後、ブラックアウト>
 ・アプリ終了状態、ホームスクリーンと同じ動作
 
<レンジング併用:アプリ終了状態、ブラックアウト>
・ビーコンを検知すると
  ・対応するアプリがバックグラウンドで起動
  ・applicationDidFinishLaunchingが呼ばれる
  ・locationManager:didEnterRegionが呼ばれる
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる
  ・バックグラウンド動作ができる約10秒の間
    ・locationManager:didRangeBeaconsが1秒毎に呼び出される


■再起動時の検出ディレイ

iPhone5Sの起動ログを見てわかったことですが、iOSデバイス再起動後、システムは約3分間かけて再起動前に登録されていたビーコンリージョンを再度登録します。したがって対応アプリは、システム起動3分後からビーコンを検知することができるようになります。


■iOS7.0から利用しているビーコンアプリについて

iOS7.1にアップデートしてから一度も起動していないiBeaconアプリでも、iOS7.0の時に起動したことがあれば、そのビーコンリージョンを憶えているようです。(iPhoneの起動ログにて、過去のビーコンリージョンが再登録されていることを確認しました)

iOS7.0の時からバックグラウンド・ビーコンの仕組み自体はすでに実装されており、iOS7.1で一般に開示されたものだと推測できます。


■アプリ終了状態で1度ビーコンを検知した後の2度目の検知

アプリ終了状態でビーコンを検知すると、次のように動作します。

<アプリ終了状態、ホームスクリーン>
・ビーコンを検知すると
  ・対応するアプリがバックグラウンドで起動
  ・applicationDidFinishLaunchingが呼ばれる
  ・locationManager:didEnterRegionが呼ばれる
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる

ここでホームボタンを2度押ししてタスクスイッチャを見ても、当該アプリは表示されません。しかしその状態で2度目のビーコンを検知すると、次のような挙動を見せます。

<アプリ終了状態、ホームスクリーン>
・ビーコンを検知すると
  ・locationManager:didEnterRegionが呼ばれる
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる

つまりタスクスイッチャには表示されていなくてもアプリはバックグラウンドで生きているわけです。


■アプリ起動時のビーコンリージョン再登録は避けましょう

アプリ終了状態でビーコンを検知した際、上記サンプルコードのようにNSUserDefaultsを使ってビーコンリージョンの再登録を避けてください。この方法を使わずにdidFinshiLaunchingWithOptionsの中で同一なビーコンリージョンをstartMonitoringForRegionすると、iOSシステム内部では登録されている同一リージョンを一度削除し、新しいリージョンとして再登録します。したがって次のような挙動になります。

<アプリ終了状態、ホームスクリーン>
・ビーコンを検知すると
  ・対応するアプリがバックグラウンドで起動
  ・applicationDidFinishLaunchingが呼ばれる
  ・locationManager:didEnterRegionが呼ばれる
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる
  ・同じビーコンリージョンの登録処理が走る
    ・登録されている同一リージョンを削除する
    ・新しいリージョンとして再登録する
  ・CLRegionStateInsideで、locationManager:didDetermineStateが呼ばれる

つまりdidDetermineStateデリゲートが続けて2回呼ばれるような動きをします。