🌀

【Swift】Flutterで作ったiOSアプリで定期的にバックグラウンドタスクを動かす

2024/01/29に公開

はじめに

Flutterでアプリを作成する際、workmanagerライブラリを使って、iOSでバックグラウンドを処理を実行しようとしました。
しかし、どうやらworkmanagerはiOS13以降のやり方(BackgroudTasks)に対応してないようで、上手く動かすことができなかったので、Swiftで直接書いたお話です。

関連するissueとPR

Workmanagerで、iOS13からのBGAppRefreshTask, BGProcessingTaskのサポートに関するissueとPRがありました。
対応しようとしている動きはあるようです。
https://github.com/fluttercommunity/flutter_workmanager/issues/518
https://github.com/fluttercommunity/flutter_workmanager/pull/511

設定する

  1. FlutterプロジェクトのiosフォルダをXcodeで開く
  2. Signing & CapabilitiesでBackground Modesを追加する
  3. Background Modesの中のBackground fetchとBackground processingをチェックする
  4. info.plistに実行したいタスクのidentifierを追加する
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
	<string>com.example.bgtask.periodic</string>
</array>

実装

Swiftでバックグラウンドタスクを実行するためのクラスを作成

  • registerBackgroundTasks: バックグラウンドタスクを登録する
  • scheduleAppRefresh: バックグラウンドタスクをスケジュールする
  • handleAppRefresh: バックグラウンドタスクを実行する
    • 実行するたびに、scheduleAppRefreshを呼び出して、次の実行をスケジュールする
import UIKit
import BackgroundTasks

class BackgroundTaskManager {
    static let shared = BackgroundTaskManager()

    func registerBackgroundTasks() {
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.bgtask.periodic", using: nil) { task in
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }
    }

    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.example.bgtask.periodic")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
        do {
            try BGTaskScheduler.shared.submit(request)
            print("Background Task submitted")
        } catch {
            print("Could not schedule app refresh: \(error)")
        }
    }

    private func handleAppRefresh(task: BGAppRefreshTask) {
        scheduleAppRefresh() // Schedule a new refresh task
        print("App is in background and app refresh task is running")
        task.setTaskCompleted(success: true)
    }
}

AppDelegateでバックグラウンドタスクを登録

application関数内で、バックグラウンドタスクを登録する。
applicationDidEnterBackground関数で、アプリがバックグラウンドになったときにバックグラウンドタスクをスケジュールする

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        BackgroundTaskManager.shared.registerBackgroundTasks()
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    override func applicationDidEnterBackground(_ application: UIApplication) {
        BackgroundTaskManager.shared.scheduleAppRefresh()
    }
}

動作確認

  1. アプリを起動してから、バックグラウンドにする
  2. Xcodeのデバッグコンソールに以下のログが出力される
Background Task submitted
  1. Xcodeで実行をポーズする。
  2. コンソールにlldbが表示されるので、以下のコマンドを実行する
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.bgtask.periodic"]
  1. 再開すると、コンソールに以下のログが出力される
Background Task submitted
App is in background and app refresh task is running

実行には以下のようにいくつかの条件があるようで、決まった時間に動かすのは難しいようです。

  • ユーザーのアプリの使用頻度
  • バッテリーやネットワークの状態
  • デバイスのパフォーマンス

せめて、デバッグ用には動いて欲しいところですが...。

Discussion