Hello, I have an application which displays the APOD (Astronomy Photo Of The Day), using NASA's API (this includes displaying the actual picture, description, author and title). I am learning how to implement Background App Refresh using the BackgroundTasks framework and simulating it using Apple's code for simulating Background App Refresh
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"TASK_IDENTIFIER"]
My Background App Refresh Task, updates the UI from the main View Controller (including the outlets), by fetching this information from the APOD API by NASA. However, when I simulate the Background App Refresh Task, the app crashes with the following message:
2023-03-01 16:43:08.394054-0600 SpacePhoto[9069:858984] SpacePhoto/ViewController.swift:73: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This message is pointed out in the following code line:
descriptionLabelOutlet.text = photoInfo.description
From what I have read, when my Background App Refresh Task is executed, the @IBOutlets are deallocated from memory and the View Controller references for such outlets are not being read, thus leading to the application crash.
How can I fix this? Is there a way to update the UI and my @IBOutlets' information using Background App Refresh?
Thank you.
Btw, here is more code to clarify the context under which I am trying to implement Background App Refresh;
Here is the code from my View Controller:
import UIKit
@MainActor
class ViewController: UIViewController, UIImagePickerControllerDelegate {
static var shared = ViewController()
var spacePhotoInfoTask: Task<Void, Never>? = nil
deinit { spacePhotoInfoTask?.cancel()}
// Access network request
let photoInfoController = PhotoInfoController()
...
@IBOutlet var descriptionLabelOutlet: UILabel!
...
// Send network request and assign the resulting String to the descriptionLabelOutlet
func updateInterface() {
spacePhotoInfoTask?.cancel()
spacePhotoInfoTask = Task {
do {
let photoInfo = try await photoInfoController.fetchPhotoInfo()
self.descriptionLabelOutlet.text = photoInfo.description
} catch {
print("Could not update extra Label with \(error)")
}
}
}
...
}
Here is the code corresponding to the AppDelegate:
import UIKit
import BackgroundTasks
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
BGTaskScheduler.shared.register(forTaskWithIdentifier: "Space", using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
scheduleAppRefresh()
print("Scheduled")
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "Space")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
// The performApplicationUpdates method, performs the UI update (method declaration included below)
let operations = Operations.performApplicationUpdates()
let lastOpertation = operations.last!
task.expirationHandler = {
queue.cancelAllOperations()
}
lastOpertation.completionBlock = {
task.setTaskCompleted(success: !lastOpertation.isCancelled)
}
queue.addOperations(operations, waitUntilFinished: false)
print("Handled App Refresh")
}
}
And finally, here is the declaration for the performApplicationUpdates method, which calls the updateInterface() method from the main View Controller:
import Foundation
import UIKit
@available(iOS 16.0, *)
struct Operations {
static func performApplicationUpdates() -> [Operation] {
let refreshHomeViewController = RefreshViewControllerInterface()
return [refreshHomeViewController]
}
}
@available(iOS 16.0, *)
class RefreshViewControllerInterface: Operation {
override func main() {
DispatchQueue.main.async {
ViewController.shared.updateInterface()
}
}
}