[iOS]バックグラウンドで長時間BLE通信続ける方法

実行環境

バックグラウンドの処理は端末や環境に左右される気がするので私の実行環境を載せておきます。

  • Swift5
  • iOS 12.2
  • iPhone XS

バックグラウンドモードの許可

Capabilities > Background Modeを選択すると、バックグラウンドで処理を長く継続できるもののリストが表示されます。

今回はBLE通信のCentral側の通信をバックグラウンドで継続したかったので、Uses Bluetooth LE accessoriesにチェックを入れました。Peripheral側の通信を行いたい場合はActs as a Bluetooth LE accessoriesにチェックを入れてください

この部分にチェックを入れるだけで、短時間の通信であればBLE通信を続けることが可能です。

しかし、バックグラウンドでの通信時間が一定以上長くなってしまった場合BLE通信が止まってしまいます。

バックグラウンドでの長時間通信方法

アプリがバックグラウンド状態になるとUIApplicationDelegateのapplicationWillResignActive(_:)が呼ばれます。

beginBackgroundTaskでバックグラウンドのアプリの処理が止まってしまうのを延長することができます。

    var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)

    func applicationWillResignActive(_ application: UIApplication) {
        //バックグラウンドで行いたい処理があるとき
        backgroundTaskID = application.beginBackgroundTask {
            [weak self] in
            application.endBackgroundTask((self?.backgroundTaskID)!)
            self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
        }
    }

    func applicationDidBecomeActive(application: UIApplication) {
        //タスクの解除
        application.endBackgroundTask(self.backgroundTaskID)
    }

しかしこれを追加しただけだと、短時間で処理が止まってしまします。

timerで呼び続ける!!!

だいぶ荒技ですが、Timerを使って一定時間ごとにbeginBackgroundTaskを呼び続ければおそらくずっと処理をし続けることが可能です。

私は以下のコードでバックグラウンド状態のアプリが80分まで処理を続けることを確認しました。

var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
var oldBackgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
var timer: Timer?

func applicationWillResignActive(_ application: UIApplication) {
        backgroundTaskID = application.beginBackgroundTask {
            [weak self] in
            application.endBackgroundTask((self?.backgroundTaskID)!)
            self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
        }

        timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true, block: { _ in
            self.oldBackgroundTaskID = self.backgroundTaskID

            // 新しいタスクを登録
            self.backgroundTaskID = application.beginBackgroundTask() { [weak self] in
                application.endBackgroundTask((self?.backgroundTaskID)!)
                self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
            }
            // 前のタスクを削除
            application.endBackgroundTask(self.oldBackgroundTaskID)
        })
    }

// フォアグラウンドになった時の処理
func applicationDidBecomeActive(_ application: UIApplication) {
    timer?.invalidate()
    application.endBackgroundTask(backgroundTaskID)
}

問題点

バックグラウンドでの処理はフォアグラウンドで行う処理より数倍速度が落ちてしまいます。

これについては『iOSAppProgrammingGuide 』の“Being a Responsible Background App” in iOS App Programming Guide に記載されていますが、バックグラウンドでの実行に時間がかかりすぎているアプリはシステムによって抑制されるか強制終了させられるとあります。

今回の私の例は強制終了される前に新しいタスクを登録しているので強制終了こそされませんが、システムに抑制されてしまい実行速度が著しく低下してしまいます。

参考文献

[Swift]バックグラウンドでも処理を続ける方法

[Swift] iOSのバックグラウンド処理について

How long does Apple permit a background task to run?

backgroundTimeRemaining

iOSでのBluetooth通信入門 :CoreBluetooth プログラミングガイド 解説 : Core Bluetoothのバックグラウンド処理(iOSアプリケーション用)

Swift

前の記事

AppDelegateを調べてみた