【Swift】 位置情報を取得するTimer処理をバックグラウンドでループさせる

会社で新しいアプリ(既存サービスのリニューアル版)を開発してるんですが、旧アプリでも実装していた「バックグラウンドで位置情報を取得する」処理がどうしてもうまくいかず、バックグラウンドに移るとTimerの処理が止まってしまう。

その関連のトコは旧アプリと同じように設定してコードも書いてるハズなのに何でだよ・・・って１日悩んでたんですが、すごく単純なコトだったので、ミニマルサンプルとして残します。

####開発環境
端末:MacBook Pro/MacOS 10.14.5(Mojave)
Xcode:10.2.1
Swift:5

####やったこと
・位置情報取得をTimerを使用してバックグラウンドでもループ処理させる


####実装
#####画面イメージ

|ループ処理の回数を画面中央に表示させるだけ|
|:-:|
|<img width="400" alt="Simulator Screen Shot - iPad Air 2 - 2019-10-25 at 12.56.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126315/4b00bfc5-d42f-8cd7-00c9-423b8ba55d4a.png">|

#####ソースサンプル

まず、Background ModesをONにして`Location updates`にチェックを入れておくのを忘れないこと。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126315/5fcb27de-800a-77ea-b54a-b7682c44be59.png">


```swift:ViewController.swift

import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
    
    var myLocationManager: CLLocationManager!
    
    var myTimer: Timer!
    
    var lblTimerCount: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // ループ処理の回数を表示するラベル
        lblTimerCount = UILabel()
        view.addSubview(lblTimerCount)
        lblTimerCount.frame = CGRect.init(x: 0, y: 0, width: 100, height: 50)
        lblTimerCount.text = String(0)
        lblTimerCount.textAlignment = .center
        lblTimerCount.center = view.center
        
        // 位置情報取得の設定
        myLocationManager = CLLocationManager()
        myLocationManager.delegate = self
        myLocationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        myLocationManager.distanceFilter = 50

        // バックグラウンドでの位置情報更新を許可
        myLocationManager.allowsBackgroundLocationUpdates = true

        // 初回起動時(位置情報サービス利用許可が設定されていない時)、許可を求めるダイアログを表示
        if (CLLocationManager.authorizationStatus() == .notDetermined) {
            myLocationManager.requestAlwaysAuthorization()
        }
        
        // ５秒ごとにlocationUpdate()を実行する
        myTimer = Timer.scheduledTimer(timeInterval: Double(5), target: self, selector: #selector(locationUpdate), userInfo: nil, repeats: true)
        myTimer.fire()
    }

    // Timerでループ実行する処理
    @objc func locationUpdate() {
        // 画面のループ処理回数の表示をカウントアップ
        var count: Int = Int(lblTimerCount.text!)!
        count += 1
        
        // stop -> start で位置情報更新させる
        myLocationManager.stopUpdatingLocation()
        myLocationManager.startUpdatingLocation()
        
        lblTimerCount.text = String(count)
        
        // コンソールでバックグラウンドでの処理実行が確認しやすいよう、現在時刻を出力
        let dt = Date()
        let dateFormatter = DateFormatter()

        dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "yMMMdHms", options: 0, locale: Locale(identifier: "ja_JP"))

        print(dateFormatter.string(from: dt))        
        print(count)
    }

    // 位置情報取得成功時
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        print("位置情報取得成功")
        
        // 以下の処理を書いてたせいで、位置情報取得を止めるだけでなく、
        // 位置情報取得しない -> バックグラウンド処理しない という判断をアプリがするらしい。
        // myLocationManager.stopUpdatingLocation()
    }
    
    // 位置情報取得失敗時
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("位置情報取得失敗")
        
        // myLocationManager.stopUpdatingLocation()
    }
}

```

CoreLocationを使用したコトある方には周知かもしれませんが、`.startUpdatingLocation`で位置情報取得開始すると、短時間でドドドドドーっと何回も位置情報取得成功(or失敗)のデリゲートが動くことがあるんですよね。

で、じゃぁ「Timerのループ間隔の中で最初にデリゲート動いた時に、位置情報取得止めちゃえばいいんじゃない？？」って考えてデリゲートの中に`.stopUpdatingLocation`を書いちゃうと、バックグラウンドで「あ、止めちゃうんだ。位置情報取得処理止めるんだったら、もうバックグラウンドでの処理自体を止めちゃうね。」っていう動きになっちゃってたみたいですね。
（新アプリの方はそういう処理の書き方でバックグラウンド処理を自ら止めちゃってました）

旧アプリの時は、「位置情報取得は動かしっぱなしにする。当然デリゲートが何回も動くけど、前回の位置情報取得からxxx秒経過してない場合は結果を破棄するね」っていう処理の書き方をしてたので、意識していないながらにちゃんとバックグラウンド処理を継続させるための処理になっていました。


#### 感想等

バックグラウンドでの処理が動かない、ってなるとどうしても設定まわりで何か足りないんじゃないかってinfo.plistを何回も見比べたりしがちなんだけど、今回の一件で

```
なんでバックグラウンド処理したいの？
→位置情報取得したいから
　→(ある処理が動いたら)位置情報取得止める
　　→じゃぁバックグランド処理する理由なくなったよね、止めるね。
```

っていう理屈と挙動が自分なりにカッチリ結びついて理解・納得できたので、１日悩んだかいがあったと思います。
それにしても、旧アプリの時はここで悩まずに済む設計してた自分、なかなかやるなぁ。。