DispatchWallTime在iOS上做了什么?

问题描述 投票:3回答:2

我认为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不同呢?

ios grand-central-dispatch
2个回答
4
投票

这个问题已经存在了很长一段时间没有任何答案,所以我想试一试,并指出我在实践中注意到的细微差别。

DispatchTime应该暂停,而DispatchWallTime应该继续运行,因为现实世界中的时钟一直在继续

你在这里是正确的,至少他们应该这样做。然而,检查DispatchTime按预期工作往往非常棘手。当iOS应用程序在Xcode会话下运行时,它具有无限的后台时间并且不会被暂停。我也无法通过在没有Xcode连接的情况下运行应用程序来实现这一点,所以如果DispatchTime在任何条件下暂停,它仍然是一个很大的问题。但是要注意的主要是DispatchTime不依赖于系统时钟。

DispatchWallTime几乎一样(它没有被暂停),除了它取决于系统时钟。为了看到差异,你可以尝试一个更长的计时器,比如5分钟。之后转到系统设置并将时间设置为1小时。如果您现在打开应用程序,您可以注意到,WallTimer立即到期,而DispatchTime将继续等待它的时间。


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为零时,它会调用_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上的“系统偏好设置”)中更改设备的时间,则会更改。

The Mysterious Six Seconds

你说你的计时器被解雇了

在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);
}

_dispatch_timeout函数可以在time.c中找到。可以说它返回当前时间和传递给它的时间之间的纳秒数。它根据传递给它的时间的时钟确定“当前时间”。

所以_dispatch_after获取在执行块之前要等待的纳秒数。然后它将leeway计算为该持续时间的十分之一。当它设置计时器的截止日期时,它会将leeway添加到您传入的截止日期。

在你的情况下,delta约为60秒(= 60 * 109纳秒),因此leeway约为6秒。因此,在调用asyncAfter后约66秒执行块。

© www.soinside.com 2019 - 2024. All rights reserved.