I am currently testing a background fetch/process with minimum functionality, which I couldn't make it happen after several days. What I originally wanted was that background task to happen every 5 or 10 minutes after clicking a button that leads to IBAction: submitBGTask, which would increment an integer saved in UserDefaults by one every time interval. If I press the button that is connected with IBAction: setLongBGTask, it is supposed to be doing a background task after 12 hours.
Also please note that I am simulating the whole process on a testflight application, not just in the simulator.
Is there any suggestion for my code I should fix to make my purpose to work?
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var status: UILabel!
@IBOutlet weak var count: UILabel!
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(didSentBG), name: NSNotification.Name("one"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didFailBG), name: NSNotification.Name("two"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didFinishBG), name: NSNotification.Name("three"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didFailSending), name: NSNotification.Name("fail"), object: nil)
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func setLongBGTask(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.scheduleLongAppRefresh()
}
@IBAction func submitBGTask(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.scheduleAppRefresh()
}
@IBAction func updateView(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
count.text = String(UserDefaults.standard.integer(forKey: "count"))
status.text = "updated"
}
@objc func didSentBG() {
status.text = "sent"
}
@objc func didFailBG() {
status.text = "fail"
}
@objc func didFinishBG() {
count.text = String(UserDefaults.standard.integer(forKey: "count"))
status.text = "finish"
}
@objc func didFailSending() {
count.text = String(UserDefaults.standard.integer(forKey: "count"))
status.text = "ff"
}
}
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UserDefaults.standard.set(0, forKey: "count")
// Override point for customization after application launch.
// 1. App Refresh Task
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.runnan.background_fetch", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.runnan.background_fetch_long", using: nil) { task in
self.handleLongAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh()
let value = UserDefaults.standard.integer(forKey: "count")
UserDefaults.standard.set(value + 1, forKey: "count")
task.expirationHandler = {
task.setTaskCompleted(success: false)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "two"), object: nil)
}
task.setTaskCompleted(success: false)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "three"), object: nil)
}
func handleLongAppRefresh(task: BGAppRefreshTask) {
UserDefaults.standard.set(20000, forKey: "count")
task.expirationHandler = {
task.setTaskCompleted(success: false)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "two"), object: nil)
}
task.setTaskCompleted(success: false)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "three"), object: nil)
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.runnan.background_fetch")
request.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60)
do {
try BGTaskScheduler.shared.submit(request)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "one"), object: nil)
// Set a breakpoint in the code that executes after a successful call to submit(_:).
} catch {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "fail"), object: nil)
print("\(Date()): Could not schedule app refresh: \(error)")
}
}
func scheduleLongAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.runnan.background_fetch_long")
request.earliestBeginDate = Date(timeIntervalSinceNow: 12 * 60 * 60)
do {
try BGTaskScheduler.shared.submit(request)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "one"), object: nil)
// Set a breakpoint in the code that executes after a successful call to submit(_:).
} catch {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "fail"), object: nil)
print("\(Date()): Could not schedule app refresh: \(error)")
}
}
}
<?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.runnan.background_fetch_long</string>
<string>com.runnan.background_process</string>
<string>com.runnan.background_fetch</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
</dict>
</plist>