我认为DispatchTime和DispatchWallTime之间的区别与应用程序是否被暂停或设备屏幕是否被锁定有关:DispatchTime应该暂停,而DispatchWallTime应继续运行,因为现实世界中的时钟继续运行。
所以我写了一个小测试应用程序:
@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.
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("backgrounding the app, starting timers for 60 seconds", Date())
DispatchQueue.main.asyncAfter(deadline: .now() + 60) {
print("deadline 60 seconds ended", Date())
}
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 60) {
print("wallDeadline 60 seconds ended", Date())
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("app coming to front", Date())
}
}
我在我的设备上运行了应用程序。我对应用程序进行了后台处理,等待了一段时间,然后将应用程序带到了前台。有时“等待一段时间”包括关闭屏幕。我得到了这样的结果:
backgrounding the app, starting timers for 60 seconds 2018-08-15 17:41:18 +0000
app coming to front 2018-08-15 17:41:58 +0000
wallDeadline 60 seconds ended 2018-08-15 17:42:24 +0000
deadline 60 seconds ended 2018-08-15 17:42:24 +0000
backgrounding the app, starting timers for 60 seconds 2018-08-15 17:42:49 +0000
app coming to front 2018-08-15 17:43:21 +0000
wallDeadline 60 seconds ended 2018-08-15 17:43:55 +0000
deadline 60 seconds ended 2018-08-15 17:43:55 +0000
deadline
计时器开火前的延迟时间并不像我预期的那么长:在60秒的截止日期之前是6秒,即使我“睡了”应用程序的时间要长得多。但更令人惊讶的是,两个计时器都在同一时刻开火。
那么wallDeadline
在iOS上做什么与deadline
不同呢?
这个问题已经存在了很长一段时间没有任何答案,所以我想试一试,并指出我在实践中注意到的细微差别。
DispatchTime应该暂停,而DispatchWallTime应该继续运行,因为现实世界中的时钟一直在继续
你在这里是正确的,至少他们应该这样做。然而,检查DispatchTime按预期工作往往非常棘手。当iOS应用程序在Xcode会话下运行时,它具有无限的后台时间并且不会被暂停。我也无法通过在没有Xcode连接的情况下运行应用程序来实现这一点,所以如果DispatchTime在任何条件下暂停,它仍然是一个很大的问题。但是要注意的主要是DispatchTime不依赖于系统时钟。
DispatchWallTime几乎一样(它没有被暂停),除了它取决于系统时钟。为了看到差异,你可以尝试一个更长的计时器,比如5分钟。之后转到系统设置并将时间设置为1小时。如果您现在打开应用程序,您可以注意到,WallTimer
立即到期,而DispatchTime
将继续等待它的时间。
The Dreams Wind的答案没有错,但我想更准确地理解这些API。这是我的分析。
DispatchTime
以下是DispatchTime.init
上面的评论:
/// Creates a `DispatchTime` relative to the system clock that /// ticks since boot. /// /// - Parameters: /// - uptimeNanoseconds: The number of nanoseconds since boot, excluding /// time the system spent asleep /// - Returns: A new `DispatchTime` /// - Discussion: This clock is the same as the value returned by /// `mach_absolute_time` when converted into nanoseconds. /// On some platforms, the nanosecond value is rounded up to a /// multiple of the Mach timebase, using the conversion factors /// returned by `mach_timebase_info()`. The nanosecond equivalent /// of the rounded result can be obtained by reading the /// `uptimeNanoseconds` property. /// Note that `DispatchTime(uptimeNanoseconds: 0)` is /// equivalent to `DispatchTime.now()`, that is, its value /// represents the number of nanoseconds since boot (excluding /// system sleep time), not zero nanoseconds since boot.
所以DispatchTime
基于mach_absolute_time
。但mach_absolute_time
是什么?它在mach_absolute_time.s
中定义。每种CPU类型都有一个单独的定义,但关键是它在类似x86的CPU上使用rdtsc
并读取ARM上的CNTPCT_EL0
寄存器。在这两种情况下,它都会获得一个单调增加的值,并且只有在处理器处于足够深度的睡眠状态时才会增加。
请注意,即使设备看起来处于睡眠状态,CPU也不一定要睡得足够深。
DispatchWallTime
在DispatchWallTime
定义中没有类似的有用的评论,但我们可以看看它的now
方法的定义:
public static func now() -> DispatchWallTime { return DispatchWallTime(rawValue: CDispatch.dispatch_walltime(nil, 0)) }
然后我们可以咨询the definition of dispatch_walltime
:
dispatch_time_t dispatch_walltime(const struct timespec *inval, int64_t delta) { int64_t nsec; if (inval) { nsec = (int64_t)_dispatch_timespec_to_nano(*inval); } else { nsec = (int64_t)_dispatch_get_nanoseconds(); } nsec += delta; if (nsec <= 1) { // -1 is special == DISPATCH_TIME_FOREVER == forever return delta >= 0 ? DISPATCH_TIME_FOREVER : (dispatch_time_t)-2ll; } return (dispatch_time_t)-nsec; }
当inval
为零时,它会调用_dispatch_get_nanoseconds
,所以let's check that out:
static inline uint64_t _dispatch_get_nanoseconds(void) { dispatch_static_assert(sizeof(NSEC_PER_SEC) == 8); dispatch_static_assert(sizeof(USEC_PER_SEC) == 8); #if TARGET_OS_MAC return clock_gettime_nsec_np(CLOCK_REALTIME); #elif HAVE_DECL_CLOCK_REALTIME struct timespec ts; dispatch_assume_zero(clock_gettime(CLOCK_REALTIME, &ts)); return _dispatch_timespec_to_nano(ts); #elif defined(_WIN32) static const uint64_t kNTToUNIXBiasAdjustment = 11644473600 * NSEC_PER_SEC; // FILETIME is 100-nanosecond intervals since January 1, 1601 (UTC). FILETIME ft; ULARGE_INTEGER li; GetSystemTimePreciseAsFileTime(&ft); li.LowPart = ft.dwLowDateTime; li.HighPart = ft.dwHighDateTime; return li.QuadPart * 100ull - kNTToUNIXBiasAdjustment; #else struct timeval tv; dispatch_assert_zero(gettimeofday(&tv, NULL)); return _dispatch_timeval_to_nano(tv); #endif }
它参考POSIX CLOCK_REALTIME
时钟。因此,它基于时间的常见概念,如果您在“设置”(或Mac上的“系统偏好设置”)中更改设备的时间,则会更改。
你说你的计时器被解雇了
在60秒的截止日期前6秒
所以让我们看看它来自哪里。
asyncAfter(deadline:execute:)
和asyncAfter(wallDeadline:execute:)
都称为相同的C API,dispatch_after
。截止日期(或“时钟”)与时间值一起被编码为dispatch_time_t
。 dispatch_after
函数调用the internal GCD function _dispatch_after
,我在此部分引用:
static inline void _dispatch_after(dispatch_time_t when, dispatch_queue_t dq, void *ctxt, void *handler, bool block) { dispatch_timer_source_refs_t dt; dispatch_source_t ds; uint64_t leeway, delta;
SNIP
delta = _dispatch_timeout(when); if (delta == 0) { if (block) { return dispatch_async(dq, handler); } return dispatch_async_f(dq, ctxt, handler); } leeway = delta / 10; // <rdar://problem/13447496> if (leeway < NSEC_PER_MSEC) leeway = NSEC_PER_MSEC; if (leeway > 60 * NSEC_PER_SEC) leeway = 60 * NSEC_PER_SEC;
SNIP
dispatch_clock_t clock; uint64_t target; _dispatch_time_to_clock_and_value(when, &clock, &target); if (clock != DISPATCH_CLOCK_WALL) { leeway = _dispatch_time_nano2mach(leeway); } dt->du_timer_flags |= _dispatch_timer_flags_from_clock(clock); dt->dt_timer.target = target; dt->dt_timer.interval = UINT64_MAX; dt->dt_timer.deadline = target + leeway; dispatch_activate(ds); }
_dispatch_timeout
函数可以在time.c
中找到。可以说它返回当前时间和传递给它的时间之间的纳秒数。它根据传递给它的时间的时钟确定“当前时间”。
所以_dispatch_after
获取在执行块之前要等待的纳秒数。然后它将leeway
计算为该持续时间的十分之一。当它设置计时器的截止日期时,它会将leeway
添加到您传入的截止日期。
在你的情况下,delta
约为60秒(= 60 * 109纳秒),因此leeway
约为6秒。因此,在调用asyncAfter
后约66秒执行块。