Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How DispatchQueue.asyncAfter behaves when application goes to background?

I would like to know how DispatchQueue.asyncAfter(deadline:execute:) behaves in case of going to background. I tried to find more information in documentation, but it's nothing there.

Let's assume case in which I invoke:
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { ... }

  1. What will happen if I go to background after let's say 10 seconds.
  2. Is it going to be finished in background mode?
  3. If not, let's assume that I will reopen the application after 2 minutes. Will it invoke the closure immediately or after remaining 50 seconds?

The most interesting part to me is to know when the timer counting down 60 seconds stops and resumes.

Update

Looking for differences between DispatchQueue.asyncAfter(deadline:execute:) and DispatchQueue.asyncAfter(wallDeadline:execute:) I've found this information from Apple Staff (source), which actually answers the question:

By the way, do you know why those two distict types are provided in Swift Dispatch library?

These model the difference between wall time (returned by gettimeofday) and Mach absolute time (returned by mach_absolute_time). The latter is independent of system clock changes but stops when you sleep. In the C API everything is flattened to Mach absolute time, which means you lose critical info. Consider what happens if you schedule a timer for 1 second in the future and then the system sleeps for 2 seconds.

To make it clear DispatchQueue.asyncAfter(deadline:execute:) uses absolute time and DispatchQueue.asyncAfter(wallDeadline:execute:) uses gettimeofday (wall time) according to this.

Here another source: What is the difference between dispatch_time and dispatch_walltime and in what situations is better to use one or the other?

Test

Based on that I prepared a test to confirm it:

override func viewDidLoad() {
    super.viewDidLoad()

    self.textView.text.append(
        "expected times: \n1. \(Date().addingTimeInterval(60.0 * 2))\n" +
        "2. \(Date().addingTimeInterval(60.0 * 3))\n" +
        "3. \(Date().addingTimeInterval(60.0 * 5))\n\n")

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0 * 2) {
        self.textView.text.append("\n\(Date()) fired based on ABSOLUTE time (1)!")
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0 * 3) {
        self.textView.text.append("\n\(Date()) fired based on ABSOLUTE time (2)")
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 60.0 * 5) {
        self.textView.text.append("\n\(Date()) fired based on ABSOLUTE time (3)!")
    }

    DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60.0 * 2) {
        self.textView.text.append("\n\(Date()) fired based on WALL time (1)!")
    }

    DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60.0 * 3) {
        self.textView.text.append("\n\(Date()) fired based on WALL time (2)")
    }

    DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60.0 * 5) {
        self.textView.text.append("\n\(Date()) fired based on WALL time (3)!")
    }
}

Result

I run it on real device with detached debugger and locked the phone. After 3.5 minutes when I restored the application there was:

fired based on WALL time (1)!
fired based on WALL time (2)!

These two events fired exactly when I restored the application. Which proves the statement above. Events based on absolute time appeared later which confirms that their timer had stopped.

like image 822
Wojciech Kulik Avatar asked Jul 26 '18 13:07

Wojciech Kulik


3 Answers

A timer started in the foreground will run in the background, if the app is already running in the background for some other reason (e.g. it is playing music and has the background mode for audio turned on, or it is performing background Core Location and has background mode for Core Location turned on).

Otherwise, it will pause in the background.

like image 70
matt Avatar answered Nov 09 '22 11:11

matt


In short, when the app is suspended (when the user goes to another app), all execution stops and this timer won’t fire unless you’ve configured it for background execution. See App programming Guide for iOS: Background Execution.

As an aside, be wary of testing this from Xcode because being attached to the debugger can change the app life cycle.

like image 3
Rob Avatar answered Nov 09 '22 11:11

Rob


It depends how your application configured the background modes. For example if you had background modes enabled and setup location monitor to always, the app can survive in background forever if user did not closed ip. So in this case the DispatchQueue.asyncAfter callback will be called.

On the other hand if background mode is disable the app will enter in freez state when is put on background, so your callback will not be called. In my tests I had notice the following flow: the dispatch queue will not stop, so if you schedule a dispatch in 60 seconds and you stay in background for 100 seconds when will come back to foreground the callback will be called immediately. If you stay in background only 30 seconds then when you will came back you will had to wait 30 more seconds.

Note you can still had background modes disabled and keep with app alive up to 3 minutes in the background if used the background task.

self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
     // called when the app is about to enter in freez state
     [[UIApplication sharedApplication] endBackgroundTask:weakSelf.backgroundTask];
}];
like image 1
Iosif Avatar answered Nov 09 '22 11:11

Iosif