技術はメシのタネ

底辺プログラマで技術の向上を目指しているけれどやりたい事が無くて困っている

【Domain=kCLErrorDomain Code=5】3つ目の原因

お晩です。
今日は仕事中に1時間ほど無駄にしたので、そのことを記録しようと思います。
てかこれ、Qiitaにも投稿しとこう。

qiita.com

Domain=kCLErrorDomain Code=5

iBeaconのビーコン領域監視アプリを作成していると、時折上記のエラーに出くわすことがあるかと思います。
これについてネットで調べると、2つ、原因が見つかります。

  • regionがnil
  • 監視しているregionが20個超

また、原因が何とは特定していなくても、iOSデバイスを再起動すれば解消される、と説明しているweb記事もあります。

ビーコンを検知しない

すでにビーコンの検知まで確認した試作アプリからビーコン検知機能をぶっこ抜いて新アプリにぶち込んだのに、ビーコンを検知しません。
何故でしょう?

原因究明のため、func locationManager(_: didStartMonitoringForRegion)requestStateForRegionを呼んでみました。
すると、即座にfunc locationManager(_: monitoringDidFailForRegion)が呼ばれるではありませんか。

教えてgoogle先生

func locationManager(_: monitoringDidFailForRegion)で検出されたエラーはDomain=kCLErrorDomain Code=5でした。

原因究明のため、printlnを仕込みます。
しかし、登録済みの監視領域を取得してみても、nilではないし、20個を超えてもいません。
iOSデバイスの再起動を行ってみても、現象は発生し続けました。

端末を変えても結果は同じ。
iOSのバージョンに関係あるかもと思い、7.1.28.3で試してみましたが、これも結果は変わりません。

原因判明

もうこうなったら仕方がないので、設定リセットしてやるぜ、と設定アプリを起動して、ようやく気が付きました。

BluetoothがONになっていません。

iBeaconはBluetooth4.0以降に統合されたBluetooth Low Energyのサブセットです。
なので、BTがONじゃないと機能が使えません。

ていうかですね、BTのONを促すアラートダイアログが悪いんですよ。

領域監視開始する際かLocationManagerのインスタンス作る時か忘れましたけど、位置情報サービスがONになってなかったらONにするダイアログ表示されますし、アプリでの位置情報の使用許諾もダイアログ表示されるんですよ。

で、BTがOFFの場合も、ダイアログ表示されるんです。
ところがね、BTのダイアログ、設定OKの二択なんですよ。
そりゃあ、よく読まなけりゃOK押しますがな。

アプリの動作確認する際に、ダイアログで位置情報もBTもONにしたと思い込んでいたけれど、実際にはBTはONじゃありませんでした、というオチです。

結論

BTがONじゃなくて位置情報だけ許可していた場合、領域監視を開始すると、Domain=kCLErrorDomain Code=5が出ます。

【原因】

  • regionがnil
  • 監視しているregionが20個超
  • BluetoothがOFF ← NEW!!!

【対処方法】

  • iOSデバイスを再起動する
  • BluetoothをONにする  ← NEW!!!

【省略コード】

// MARK:CLLocationManagerDelegate
extension ViewController {
    // CLLocationManagerインスタンスを生成すると呼ばれる
    func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
        // 領域監視開始
        manager.startMonitoringForRegion(myRegion)

        // デバッグ用
        for region in manager.monitoredRegions {
            println("monitoring region count \(manager.monitoredRegions.count)")
            println(region)
        }
    }
    
    // startMonitoringForRegionに対するデリゲート
    func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
        // 監視状況のチェック
        // エラー発生時、monitoringDidFailForRegionに拾わせるため
        manager.requestStateForRegion(myRegion)
    }
    
    // requestStateForRegionに対するデリゲート
    // requestStateForRegionを呼んでなくても領域を検知した場合は呼ばれる
    func locationManager(manager: CLLocationManager!, didDetermineState state: CLRegionState, forRegion region: CLRegion!) {
        println("didDetermineState: \(state.name)")
    }
    
    // startMonitoringForRegion後のエラーを拾う
    func locationManager(manager: CLLocationManager!, monitoringDidFailForRegion region: CLRegion!, withError error: NSError!) {
        println("monitoringDidFailForRegion")
        // withError: Error Domain=kCLErrorDomain Code=5
        // 1. region が nil
        // 2. 監視している region が 20 個を超えた
        // 3. Bluetooth を ON にしてない
    }
}
extension CLRegionState {
    var name : NSString {
        get{
            var enumName = "CLRegionState"
            var valueName = ""
            
            switch self {
            case .Unknown:
                valueName = enumName + "Unknown"
            case .Inside:
                valueName = enumName + "Inside"
            case .Outside:
                valueName = enumName + "Outside"
            }
            return valueName
        }
    }
}

BLEのペリフェラルを今更実装してみた(iOS編)

「CoreBluetoothのEnum値」
ログ出し用のnameプロパティ、重宝させていただいております。

[Swift]switch文のcaseやdefault節で何も処理しない場合はbreakが必要

自分もそう思っていました。
でも上記のname拡張で、変だなって。
なるほど、switchは取り得る値全てをカバーしていればdefaultは必要ないんですね。

以上