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


iBeaconのビーコン領域監視アプリを作成していると、時折下記のエラーに出くわすことがあるかと思います。

`Domain=kCLErrorDomain Code=5`

これについてネットで調べると、２つ、原因が見つかります。

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

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

上記２件の原因のいずれにも当てはまらず、iOSデバイスの再起動でも`Domain=kCLErrorDomain Code=5`が解消されなかったので、その解決方法を記そうと思います。

## 現象
* ビーコンを検知しない
* `func locationManager(_: monitoringDidFailForRegion)`で`Domain=kCLErrorDomain Code=5`

## 原因
* BluetoothがOFF

領域監視開始（CLLocationManagerインスタンス生成？）時に出るダイアログで位置情報サービスは`ON`にしてあったし、アプリの位置情報の許諾も出していたんです。
が、BTのダイアログで`OK`を選んでしまったため（`設定`と`OK`の二択）、BTがOFFのまま……。
それに気付かず延々１時間、ネット徘徊とログ取りを繰り返してしまいました。
偶然でも原因に気が付けて良かったです。

上の話は仕事中にあった出来事です。
下記コードは家に帰ってから記憶を頼りに適当に打ったものですので、動作確認等はしていません。
コンパイルエラーは出ていませんが(Xcode6.3)。
ターゲットはiOS7.1を想定しているのでAuthorizationの処理が甘いです。
あ、UUIDだけは変えました。

```Swift
import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {

    var myLocation: CLLocationManager!
    var myRegion: CLBeaconRegion!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        myLocation = CLLocationManager()
        myLocation.delegate = self
        myRegion = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "uuidgenで求めてください"), identifier: "hoge")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

// 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編)](http://qiita.com/__moai/items/84e5789342840d862305)

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

[[Swift]switch文のcaseやdefault節で何も処理しない場合はbreakが必要](http://qiita.com/wao9@github/items/3ee37cc133b7e4a68682)

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

以上です。