当应用程序进入后台时,DispatchQueue.asyncAfter的行为如何?

13
我想知道DispatchQueue.asyncAfter(deadline:execute:)在进入后台时的行为。我尝试在documentation中找到更多信息,但没有找到任何内容。
假设我调用以下内容:
DispatchQueue.main.asyncAfter(deadline: .now() + 60.0) { ... }
  1. 如果我在10秒后进入后台会发生什么?
  2. 它会在后台模式下完成吗?
  3. 如果不是,假设我在2分钟后重新打开应用程序。它会立即调用闭包还是等待剩余50秒后再调用?
对我来说最有趣的部分是了解倒计时60秒何时停止和恢复。

更新

寻找DispatchQueue.asyncAfter(deadline:execute:)DispatchQueue.asyncAfter(wallDeadline:execute:)之间的区别,我从Apple员工(source)那里找到了这些信息,实际上回答了这个问题:
顺便问一下,你知道为什么Swift Dispatch库提供了这两种不同的类型吗?它们模拟了墙上时间(由gettimeofday返回)和Mach绝对时间(由mach_absolute_time返回)之间的差异。后者独立于系统时钟更改,但在睡眠时停止。在C API中,所有内容都被压缩为Mach绝对时间,这意味着您会丢失关键信息。考虑一下如果您安排一个定时器在未来1秒触发,然后系统睡眠2秒会发生什么。
为了清楚起见,DispatchQueue.asyncAfter(deadline:execute :)使用绝对时间,而DispatchQueue.asyncAfter(wallDeadline:execute :)使用gettimeofday(墙上时间)。
这里是另一个来源:dispatch_time和dispatch_walltime之间的区别是什么?在什么情况下最好使用其中之一? 测试
基于此,我准备了一个测试来确认它:
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)!")
    }
}

结果

我在真实设备上使用分离式调试器运行它并锁定手机。 3.5分钟后,当我恢复应用程序时,出现了:

基于WALL时间触发(1)!
  基于WALL时间触发(2)!

这两个事件恰好在我恢复应用程序时触发。 这证明了上面的说法。 基于绝对时间的事件稍后出现,这证实了它们的计时器已停止。


试一下并告诉我们 :) - Prashant Tukadiya
2
@PrashantTukadiya 当然我可以测试它,但是在后台运行的情况下,少数测试并不能证明什么 :) 我更愿意知道至少在底层它做了什么。 - Wojciech Kulik
3个回答

5

如果应用程序已经因为其他原因在后台运行(例如正在播放音乐并且已经开启了音频后台模式,或正在执行后台定位并已经开启了 Core Location 后台模式),那么在前台开始的定时器将在后台运行。

否则,它将在后台暂停。


2
正如Rob所说,您无法从Xcode中测试此功能,特别是在模拟器中,因为它会错误地处理后台行为。 - matt
没错,我已经找到了这些信息的来源(请查看我的更新问题),并且已经亲自验证过了。 - Wojciech Kulik
数据包隧道提供者扩展怎么样?在iOS 13中,我似乎观察到了一些变化,它会挂起任何DispatchQueue代码,直到设备唤醒,这对我来说听起来很有bug。仍然需要100%的确认,但欢迎分享经验。 - keeshux

3
简而言之,当应用程序被挂起时(当用户转到另一个应用程序时),所有执行都会停止,除非您已将其配置为后台执行。请参见iOS应用程序编程指南:后台执行
另外,要小心从Xcode进行测试,因为附加到调试器可能会更改应用程序的生命周期。

1
这取决于您的应用程序如何配置后台模式。例如,如果启用了后台模式并设置位置监视器为“始终”,则如果用户未关闭IP,则该应用程序可以永久在后台生存。因此,在这种情况下,将调用DispatchQueue.asyncAfter回调函数。
另一方面,如果禁用后台模式,则将应用程序置于后台时会进入冻结状态,因此不会调用回调函数。在我的测试中,我注意到以下流程:调度队列不会停止,因此如果您安排了60秒的调度,并且在后台停留了100秒,当回到前台时,回调将立即被调用。如果您只停留在后台30秒,则回来时还需要等待30秒。
请注意,即使禁用了后台模式,仍然可以在后台使用后台任务使应用程序保持活动状态长达3分钟。
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
     // called when the app is about to enter in freez state
     [[UIApplication sharedApplication] endBackgroundTask:weakSelf.backgroundTask];
}];

关于第二段,你有这些信息的来源吗?还是这是你的经验?此外,我们称其为“计时器”,但我不知道DispatchQueue.main.asyncAfter内部实际发生了什么,所以澄清一下,答案与Timer无关,而是与DispatchQueue有关,对吗? - Wojciech Kulik
我的第二段陈述是基于在设备上运行的几个测试。 - Iosif
不幸的是,第二段不是真的。请查看我的更新问题。这就是为什么我在问题下面的评论中指出,测试它并不能证明任何事情,最好找出它在底层是如何工作的,然后知道了这一点再进行测试 :) 但是感谢您对这个答案的努力。 - Wojciech Kulik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接