2024年10月21日月曜日

SwiftUI + BackgroundTasks を試してみた

SwiftUI + BackgroundTasks を試してみた

概要

SwiftUI で Delegate を定義してその中でバックグランド処理を定義します
アプリがバックグランドに移動するとタスクがキューイングされ実行されます

Swift の BackgroundTasks の最大のポイントは自分で実行する時間を制御できない点です
iOS 側のリソース状況によって実行時間がだいぶ変わるのですぐには実行されません

環境

  • macOS 15.0.1
  • Xcode 16.0

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>BGTaskSchedulerPermittedIdentifiers</key>
        <array>
                <string>com.example.app.refresh</string>
        </array>
        <key>UIBackgroundModes</key>
        <array>
                <string>fetch</string>
        </array>
</dict>
</plist>

サンプルコード

実際にタスクが実行されるのは iOS 側の制御になるので指定の時間や指定の間隔で必ず実行されないことに注意です

import SwiftUI
import BackgroundTasks

@main
struct testApp: App {
    // SwiftUI における AppDelegateの設定
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) {
            if scenePhase == .background {
                // バックグラウンドに入る際にタスクをスケジュール
                appDelegate.scheduleAppRefresh()
            }
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // バックグラウンドタスクの登録、ここのコールバックメソッドは実際にタスクが実行される際に呼ばれます
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.app.refresh", using: nil) { task in
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }
        return true
    }
    
    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
        // 10秒後に実行するようにしているが実際に実行されるのはアプリがバックグランドに移動してからタスクが実行可能になった10秒後に実行される
        // バックグランドに移動してから10秒後ではないので注意
        request.earliestBeginDate = Date(timeIntervalSinceNow: 10)
        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Unable to schedule app refresh: \(error)")
        }
    }
    
    // 実際にタスクが実行された際にタスクの実行結果のハンドリングを行うメソッド
    func handleAppRefresh(task: BGAppRefreshTask) {
        task.expirationHandler = {
            print("Task expired")
        }
        let operation = BlockOperation {
            print("Background task is running")
        }
        operation.completionBlock = {
            task.setTaskCompleted(success: !operation.isCancelled)
            print("Task completed: \(operation.isCancelled ? "Cancelled" : "Success")")
        }
        OperationQueue().addOperation(operation)
    }
}

最後に

今回のサンプルコードではアプリがバックグランドに移動するたびにタスクがキューイングされるので定期的に実行されるようなタスクではありません

そもそも iOS ではバックグランド処理はリソースを占有する処理になるため悪とされているので可能な限り使わないほうがいいのかなと思います

Debug -> Simulate Background fetch で実行して 15 分後くらいにデバッグログが表示されました

0 件のコメント:

コメントを投稿