【Swift】バックグラウンドで処理を続ける方法!beginBackgroundTask
この記事からわかること
- Swiftでバックグラウンドで処理を続けるには?
- UIApplicationクラスのbeginBackgroundTaskメソッドの使い方
- Background Task 2 ("Called by MyApp, from $XXX"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.とは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
バックグラウンドでも処理を継続する方法
Swiftではバックグラウンド(ここではアプリが停止している状態)でも処理を継続させる方法がいくつか存在するようです。そもそもバックグラウンドでもアプリを実行させることが不毛なバッテリー消費を引き起こすことになるためiOSアプリではバックグラウンド操作の制限が厳しいようです。
今回はその中でもUIApplication
クラスのbeginBackgroundTask
メソッドを使用した方法をまとめていきます。
beginBackgroundTaskメソッド
公式リファレンス:beginBackgroundTaskメソッド
func beginBackgroundTask(expirationHandler handler: (() -> Void)? = nil) -> UIBackgroundTaskIdentifier
beginBackgroundTask
はアプリがバックグラウンドに入った場合に続行したいタスクをマークするためのメソッドです。返り値としてバックグラウンドタスクを一意に識別するUIBackgroundTaskIdentifier
型のIDを返します。このIDは明示的にバックグラウンドタスクが完了したことをマークするendBackgroundTask
メソッドの引数として利用します。
引数expirationHandler
は動作可能なバックグラウンド動作時間が0になる直前に呼び出されるハンドラーです。この方法ではバックグラウンドで処理を継続できる秒数が制限されているので、制限時間を超えないような処理を渡すように注意する必要があります。具体的な秒数を検証してみましたが、シミュレーターでは30秒程度、実機では10分以上動作することを確認できました。
使用方法
使用方法はバックグラウンドで実行したい処理の前で呼び出すだけです。バックグラウンドでの動作が不要になるタイミングでendBackgroundTask
メソッドを呼び出します。
private func backgroundCount() {
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
}
timerPublisher = Timer.publish(every: 1, on: .current, in: .common)
.autoconnect()
.receive(on: DispatchQueue(label: "subthread", qos: .background))
.sink { _ in
print("発火!\(count)")
count += 1
if count == 1000 {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
}
}
}
Swift UIで動作確認するためのコードは以下の通りになります。
import SwiftUI
import Combine
struct ContentView: View {
@State private var count: Int = 0
@State var timerPublisher: AnyCancellable?
@State private var backgroundTaskID = UIBackgroundTaskIdentifier(rawValue: 0)
private func backgroundCount() {
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
}
timerPublisher = Timer.publish(every: 1, on: .current, in: .common)
.autoconnect()
.receive(on: DispatchQueue(label: "subthread", qos: .background))
.sink { _ in
print("発火!\(count)")
count += 1
if count == 1000 {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
}
}
}
@Environment(\.scenePhase) private var scenePhase
var body: some View {
VStack {
Text("\(count)")
.onAppear {
backgroundCount()
}.onChange(of: scenePhase) { phase in
switch phase {
case .active:
print("フォアグラウンド")
case .inactive:
print("フォアグラウンド(作業を一時停止する必要)")
case .background:
print("バックグラウンド")
@unknown default:
print("不明")
}
}
}
}
}
Background Task 2 ("Called by MyApp, from $XXX"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.
beginBackgroundTask
メソッドを利用していると以下のようなログが出力されることがあります。これは適切にendBackgroundTask
メソッドを呼び出していないことが原因です。
Background Task 2 ("Called by MyApp, from $XXX"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.
適切にタスクを終了させない場合、アプリがOSによって終了させられることもあるので注意してください。
Bluetoothではもっと簡単にバックグラウンド処理を実装できます
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。