SwiftUI 上的 BackgroundTasks 和 BGTaskScheduler 未触发
我正在尝试设置一些 BackgroundTasks
在应用程序关闭时定期运行一些代码(比如每天一次)。我相信 swift 上的 BackgroundTasks
API 就是为了这个? (如果我弄错了,请告诉我)。我按照Apple文档上此处的文章来实现它,并且调整它以适应 SwiftUI。
问题:后台任务永远不会触发,而是“待处理”(如图所示)
免责声明:我确实添加了后台模式
功能,并且检查了后台获取
和后台处理
,并将标识符添加到Info.plist
代码:
Main
- 设置应用程序委托,获取场景,设置 UserDefaults 计数器,获取所有待处理任务的按钮,显示 UserDefault 计数器的文本,添加到 UserDefault 计数器的按钮,当应用进入后台时调用 schedule()
任务
import SwiftUI
import BackgroundTasks
@main
struct GyfterApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var scene
@AppStorage("test") var test = 1
var body: some Scene {
WindowGroup {
VStack {
Button("Test") {
BGTaskScheduler.shared.getPendingTaskRequests { all in
print("Pending Tasks Requests", all)
}
}
Text("\(test)")
Button("ADD") {
test = test + 1
}
}
.onChange(of: scene) { newValue in
switch newValue {
case .background:
print("Entered Background")
appDelegate.schedule()
default:
break
}
}
}
}
}
操作 -
BackgroundTasks
的操作,它打印一条消息并向 UserDefaults 计数器加 1
class OP: Operation {
@AppStorage("test") var test = 1
override func main() {
print("OPERATION RAN")
test = test + 1
}
}
AppDelegate
-
- 使用给定的标识符注册任务,当它出现时打印消息运行,并打印注册调用的输出以查看它是否已注册(它已注册但未调用
handleSchedule(task:)
) - 使用标识符安排任务请求,提前 60 秒,提交到调度程序(
schedule()
仅在应用程序进入后台时调用,而不是在handleSchedule(task:)
应该触发但永远不会被调用时调用) - handleSchedule(task:)打印声明,调用
schedule()
,创建OperationQueue
,创建Operation
,设置expirationHandler
和setTaskCompleted
,将操作添加到队列
class AppDelegate: NSObject, UIApplicationDelegate {
@AppStorage("test") var test = 1
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("LAUNCHED")
let a = BGTaskScheduler.shared.register(forTaskWithIdentifier: "IDENTIFIER", using: nil) { task in
print("REGISTERED")
self.test = self.test + 1
self.handleSchedule(task: task as! BGAppRefreshTask)
}
print(a)
return true
}
func schedule() {
let request = BGAppRefreshTaskRequest(identifier: "IDENTIFIER")
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleSchedule(task: BGAppRefreshTask) {
print("HANDLING SCHEDULE")
schedule()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let operation = OP()
task.expirationHandler = {
print("BG Task Expired")
queue.cancelAllOperations()
}
operation.completionBlock = {
print("OPERATION COMPLETED")
task.setTaskCompleted(success: !operation.isCancelled)
}
queue.addOperation(operation)
}
}
控制台日志:显示函数调用的打印语句
- 第1行:应用程序启动
- 第2行:
BGTaskScheduler
“注册”(假设)任务标识符 - 第 3 行:待处理任务的数组
- 第 4 行:应用程序进入后台(并且
schedule()
被调用) - 第 5 行:待处理任务的数组
LAUNCHED
true
Pending Tasks Requests []
Entered Background
Pending Tasks Requests [<BGAppRefreshTaskRequest: INDENTIFIER, earliestBeginDate: 2022-02-28 02:57:50 +0000>]
I am trying to setup some BackgroundTasks
to run some code periodically (say once every day) while the app is closed. I believe the BackgroundTasks
API on swift is just for that? (Please let me know if I'm mistaken). I followed the article here on Apple's Docs to implement it, and adjusting it to fit SwiftUI.
Problem: The background task never fires but is "pending" (as seen in the images)
Disclaimer: I did add the Background Modes
capability and checked Background fetch
and also Background processing
, and added the identifer to the Info.plist
Code:
Main
- Setup app delegate, get scene, set UserDefaults counter, button to get all pending tasks, text showing the UserDefault counter, Button to add to UserDefault counter, call the schedule()
task when app enters background
import SwiftUI
import BackgroundTasks
@main
struct GyfterApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var scene
@AppStorage("test") var test = 1
var body: some Scene {
WindowGroup {
VStack {
Button("Test") {
BGTaskScheduler.shared.getPendingTaskRequests { all in
print("Pending Tasks Requests", all)
}
}
Text("\(test)")
Button("ADD") {
test = test + 1
}
}
.onChange(of: scene) { newValue in
switch newValue {
case .background:
print("Entered Background")
appDelegate.schedule()
default:
break
}
}
}
}
}
Operation
- Operation for the BackgroundTasks
, it prints a message and adds 1 to the UserDefaults counter
class OP: Operation {
@AppStorage("test") var test = 1
override func main() {
print("OPERATION RAN")
test = test + 1
}
}
AppDelegate
-
- Register task with given identifier, print message when it runs, and print the output of the register call to see if it was registered (it gets registered but doest not call
handleSchedule(task:)
) - Schedule the task request with identifier, for 60 seconds ahead, submit to the scheduler (
schedule()
only gets called when app enters background, not whenhandleSchedule(task:)
is supposed to fire which never gets called) - handleSchedule(task:) print statement, call
schedule()
, createOperationQueue
, createOperation
, setexpirationHandler
andsetTaskCompleted
, add operation to the queue
class AppDelegate: NSObject, UIApplicationDelegate {
@AppStorage("test") var test = 1
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("LAUNCHED")
let a = BGTaskScheduler.shared.register(forTaskWithIdentifier: "IDENTIFIER", using: nil) { task in
print("REGISTERED")
self.test = self.test + 1
self.handleSchedule(task: task as! BGAppRefreshTask)
}
print(a)
return true
}
func schedule() {
let request = BGAppRefreshTaskRequest(identifier: "IDENTIFIER")
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleSchedule(task: BGAppRefreshTask) {
print("HANDLING SCHEDULE")
schedule()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let operation = OP()
task.expirationHandler = {
print("BG Task Expired")
queue.cancelAllOperations()
}
operation.completionBlock = {
print("OPERATION COMPLETED")
task.setTaskCompleted(success: !operation.isCancelled)
}
queue.addOperation(operation)
}
}
Console log: Display of the print statements from the function calls
- Line 1: App launched
- Line 2:
BGTaskScheduler
"registered" (supposedly) the task with identifier - Line 3: Array of the pending tasks
- Line 4: App entered background (and
schedule()
got called) - Line 5: Array of the pending tasks
LAUNCHED
true
Pending Tasks Requests []
Entered Background
Pending Tasks Requests [<BGAppRefreshTaskRequest: INDENTIFIER, earliestBeginDate: 2022-02-28 02:57:50 +0000>]
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我将首先通过强制后台任务在设备上运行来调试它,以便您知道它是否成功:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"IDENTIFIER"]
但是,我的预感是,当您离开作用域时,您的操作队列只是被释放,因为它没有连接对任何事情。您可能需要
waitUntilAllOperationsAreFinished()
以确保此代码不会因队列操作而退出作用域。I would debug this first by forcing the background task to run on device so you know its succeeding:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"IDENTIFIER"]
However, my hunch is that your operation queue is simply being deallocated as you leave scope since it's not hooked up to anything. Possibly you need to
waitUntilAllOperationsAreFinished()
to ensure this code doesn't exit scope with a queue'd operation.