31484


【Tips】iOSで位置情報を取得する方法（Swift 3.0対応）  
<http://blog.koogawa.com/entry/2016/03/26/181906>  
バックグラウンドでの定期的な処理について  
<https://ja.stackoverflow.com/a/8513/18443>

上記2サイトの処理をくっつけて「Timerを利用して、バックグラウンドでも位置情報を1秒ごとにprint出力するアプリ」をつくりたいのですが、アプリをバックグラウンドにすると、5秒ほどしてからprint出力がストップしてしまいます。  
どこでミスが生じているのか、教えてください。

[code]

    import UIKit
    import CoreLocation
    
    class ViewController: UIViewController, CLLocationManagerDelegate {
    
        @IBOutlet weak var latTextField: UITextField!
        @IBOutlet weak var lngTextField: UITextField!
    
        var locationManager: CLLocationManager!
        var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            if CLLocationManager.locationServicesEnabled() {
                locationManager = CLLocationManager()
                locationManager.delegate = self
                backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
                    UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
                })
                var timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: "update", userInfo: nil, repeats: true)
            }
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
    
            if CLLocationManager.locationServicesEnabled() {
                locationManager.stopUpdatingLocation()
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        // MARK: - CLLocationManager delegate
        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            switch status {
            case .notDetermined:
                locationManager.requestWhenInUseAuthorization()
            case .restricted, .denied:
                break
            case .authorizedAlways, .authorizedWhenInUse:
                break
            }
        }
    
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            guard let newLocation = locations.last,
                CLLocationCoordinate2DIsValid(newLocation.coordinate) else {
                    self.latTextField.text = "Error"
                    self.lngTextField.text = "Error"
                    return
            }
    
            print("".appendingFormat("%.4f", newLocation.coordinate.latitude))
            self.latTextField.text = "".appendingFormat("%.4f", newLocation.coordinate.latitude)
            self.lngTextField.text = "".appendingFormat("%.4f", newLocation.coordinate.longitude)
        }
    
        func update(){
            locationManager.startUpdatingLocation()
        }
    }
    
[/code]


CC BY-SA 3.0
2016-12-27T16:52:14.007
31477
2016-12-28T02:09:07.337
2017-04-13T12:52:39.113
-1
18443
question
swift ios swift3
Timerを利用したバックグラウンドでの位置情報の取得


結論から言うと、iOSで「Timerを利用して、バックグラウンドでも位置情報を1秒ごとにprint出力するアプリ」を作成することは不可能です。iOSの位置情報サービスの仕様に合わせてアプリの機能設計・仕様を見直してください。

* * *

リンクの2つめの記事ですが、大変実験的な結果を極めて限定的な条件で試された結果しか載っていません。Simulator以外では動くのか、(動いたとして)App
Storeに承認されるのかなどの情報がありませんし、現実問題としてsimulatorではバックグラウンド動作が実機と異なるので、「simulatorではうまくいった」で終わっている記事はあてになりません。

リンク先のコードをSwift 3に置き換えて試してみました。

[code]

    import UIKit
    
    class ViewController: UIViewController {
        var backgroundTaskIdentifier: UIBackgroundTaskIdentifier = 0
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask {
                UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier)
            }
            Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(update), userInfo: nil, repeats: true)
    
        }
    
        func update() {
            print(Date(), UIApplication.shared.backgroundTimeRemaining)
        }
    }
    
[/code]

`update()`の出力中に`UIApplication.shared.backgroundTimeRemaining`でバックグランド実行できる残り時間を表示させるようにしてみました。

実機(iOS 9.2)での実行結果はこちら:

[code]

    ...
    2016-12-28 0午前0:05:53 +0000 1.79769313486232e+308
    2016-12-28 0午前0:05:54 +0000 1.79769313486232e+308
    (ここでHomeを押してバックグラウンドに移行)
    2016-12-28 0午前0:05:55 +0000 179.780146666657
    2016-12-28 0午前0:05:56 +0000 178.829380999989
    2016-12-28 0午前0:05:57 +0000 177.805755624984
    ...
    2016-12-28 0午前0:08:48 +0000 6.74223999999231
    2016-12-28 0午前0:08:49 +0000 5.78680704167346
    2016-12-28 0午前0:08:50 +0000 4.82001395832049
    (ここで出力終了)
    
[/code]

ご覧のようにおよそ180からカウントダウンが始まり、カウントダウンが0になる少し前に実行されなくなります。この方法によるバックグラウンド処理の延長は3分が限度、と言う情報が正しく実機での状況を表しているようです。

(時刻表示が変なのは環境設定によってはこうなっちゃうこともあると言う例だと思ってください。)

* * *

では、なぜあなたの処理は3分も保たずに「5秒ほど」で処理されなくなったのかというと原因はこちらでしょう。

[code]

    locationManager.requestWhenInUseAuthorization()
    
[/code]

`WhenInUse`と言うのは「使用中」のことで、「アプリの使用中は位置情報を取得する」と言う要求を出しているので、アプリがバックグランドに回る⇒使用中ではない⇒位置情報も取得できない、と言う状態なのだろうと思われます。

ここを

[code]

    locationManager.requestAlwaysAuthorization()
    
[/code]

と置き換えれば、もう少し長く動き続ける可能性はありますが、こちらでテストした限り、それも実機がスタンバイ状態になるまでで、「最長でも3分、スタンバイ状態に入ればそれより早く終了」と言う形になりました。

そもそも`locationManager.startUpdatingLocation()`の処理は数秒かかることもあるという重い処理で、それを1秒ごとに呼び出すなんてことは
**Appleの想定していないとんでもない使い方**
と言うことになります。(一度起動してやると、あとはiOSが裏でバッテリー消費とかにも気をつけて位置の更新を検出してくれるのでそれを待つ、と言うのが想定された使い方。)

* * *

位置情報の取得というのは、「[iOSアプリケーション
プログラミングガイド](https://developer.apple.com/jp/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html)」にも記載されている「バックグラウンドでの動作が許可されている処理」の一つ([バックグランド実行](https://developer.apple.com/jp/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW1)参照)ですが、それも各処理をAppleの想定する使い方に従って処理する場合でのこと、Appleがレビュー時に「とんでもない使い方」を検知できるかどうかはわかりませんが、無理のある仕様で裏技的な解決策を求めるよりは、正攻法で「位置情報をバックグラウンドで取得する」方法を探し出して、その方法と制約のもとでアプリの仕様を見直した方が良いのではないでしょうか。


CC BY-SA 3.0
2016-12-28T02:08:13.180
31484
2016-12-28T02:08:13.180
13972
31477
answer