我认为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
在与deadline
有何区别的iOS上做什么?
答案 0 :(得分:4)
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
为nil时,它将调用_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); }
可以在time.c
中找到_dispatch_timeout
函数。可以说,它返回当前时间和传递给它的时间之间的纳秒数。它根据传递给它的时间的时钟来确定“当前时间”。
因此_dispatch_after
获得执行块之前要等待的纳秒数。然后,它将leeway
计算为该持续时间的十分之一。在设置计时器的截止时间后,它将leeway
添加到您传入的截止时间。
在您的情况下,delta
约为60秒(= 60 * 10 9 纳秒),因此leeway
约为6秒。因此,您在调用asyncAfter
后大约66秒执行了代码块。
答案 1 :(得分:3)
这个问题已经存在了很长时间,没有任何答案,所以我想尝试一下,指出我在实践中发现的细微差别。
DispatchTime应该暂停,而DispatchWallTime应该继续前进 因为现实世界中的时钟一直在运行
您在这里是正确的,至少他们应该这样做。但是,检查DispatchTime是否按预期工作通常是非常棘手的。当iOS应用在Xcode会话下运行时,它具有无限的后台时间,并且不会被挂起。我也无法通过在未连接Xcode的情况下运行应用程序来实现这一点,因此在任何情况下DispatchTime是否暂停仍然是一个大问题。但是,要注意的主要事情是 DispatchTime不依赖于系统时钟。
DispatchWallTime 的工作原理几乎相同(未挂起),除了取决于系统时钟。为了了解差异,您可以尝试使用更长的计时器,例如5分钟。之后,转到系统设置并将时间设置为向前1小时。如果现在打开应用程序,您会注意到,WallTimer
立即到期,而DispatchTime
将继续等待其时间。