Smart phones reinforces multitasking possible with all extends. In such cases the app enters to non-foreground (background & suspended) state from various user interactions such as pressing home button, switching apps or even when it could be simply pressing from power button (or Even it could be from System events). These events can cause a suspended app to be returned to the background, or cause a not running app to be launched directly into the background. System events can cause a suspended app to be returned to the background, or cause a not running app to be launched directly into the background.
Why I am stressing these points ?
In my recent app I introduced a new feature where I need to:
- Show a timer that ticks every seconds.
- A sound file that plays on each seconds.
- A local notification has to fire on completion of every timer.
All the above has to be run in background state without interrupting timer progress bars when the user switches back to app. The existing logic of UI elements, animations, time triggers are tightly coupled with UI layer. Uhh.. !!
Stepping into solution :
At first I thought - cache timer values in user defaults when applicationDidEnterBackground(_:) notification fired from UIApplication. Later resume when ‘willEnterForgroundState’ called. Since it was not feasible & gives boiler plate code for resuming UI states and also timer tick sound & notification timings will not be in sync . More over ‘applicationDidEnterBackground(_:)’ method gives at most 5 seconds to perform my tasks and return.
A few moment later after some SO searches, I extended app’s runtime by calling the beginBackgroundTask(withName:expirationHandler:) method. Calling this method gives you extra time to perform important tasks. (We can find out how much time remains using the backgroundTimeRemainingproperty.) When you finish your tasks, call the endBackgroundTask(_:) method right away to let the system know that you are done. If you do not end your tasks in a timely manner, the system terminates your app.
Along with Apple’s Background mode enabled from app capabilities:
✔️Enabled mode - Audio, AirPlay and Picture in Picture.
Keeping all the above in mind, I wrote below pseudo code that helped me to execute background tasks in a simple block. Entire timer logic was under the same block & along GCD main thread block for UI updates when user switches back to foreground.
class BackgroundExecutable { var identifier: UIBackgroundTaskIdentifier let function: (() -> Void) -> Void init(function: @escaping (_ completion: () -> Void) -> Void) { self.function = function self.identifier = UIBackgroundTaskInvalid } func execute() { let application = UIApplication.shared identifier = application.beginBackgroundTask { application.endBackgroundTask(self.identifier) } function(endBackgroundTask) } func endBackgroundTask() { UIApplication.shared.endBackgroundTask(identifier) } }All I need to write a block of code that will handle my timer logic.
let bgTask = BackgroundExecutable { (f) in //Timer Logic goes here... DispatchQueue.main.async { [unowned self] in //Do UI updates here when app in foreground } } bgTask.execute() // when the timer completed in some point of app. bgTask.endBackgroundTask()