DispatchWallTime在iOS上做什么?

时间:2018-08-15 17:53:28

标签: ios grand-central-dispatch

我认为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上做什么?

2 个答案:

答案 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_tdispatch_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将继续等待其时间。