NSTimer 在 firedate 之前触发

NSTimer fires before the firedate

我有下面的代码,它根据存储在 NSDefault 变量 checkOutTime 中的值初始化一个 NSTimer。

假设我将此变量中的时间更改为过去的时间(在当前时间之前)并重新初始化计时器,它会自动触发并调用 ForceCheckOut() 回调方法。

我需要定时器只在给定的触发日期触发。

func InitilizeAutoCheckOut(isExtendedCheckout:Bool)
{

        let checkOutTime = self.defaults.valueForKey("checkOutTime") as! String

        print(checkOutTime)



        if !checkOutTime.isEmpty
        {
            let date = self.StringToDate(checkOutTime, IsExtendedCheckout: isExtendedCheckout)

            print(date)

            let autoCheckOutTimer = NSTimer(fireDate: date, interval: 24*60*60, target: self, selector: #selector(DashboardViewController.ForceCheckOut), userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(autoCheckOutTimer, forMode: NSDefaultRunLoopMode)

        }

}

正如在几个地方 (here is a good example) 中所解释的,NSTimerfireDate 实际上并不是计时器触发的日历日期时间。将其视为一种请求倒计时的机制更为准确。值得注意的是,NSTimer 不考虑时钟重置、背景等。它基本上计算一个数字,直到它应该触发,定期递减该数字,并在该数字 <= 0 时触发。更改本地时钟不会t 影响那个数字,暂停进程(例如,通过后台运行应用程序)将导致倒计时从当前值恢复,而不考虑它睡着的时间。这可以说是不直观的,因为 fireDate 强烈暗示 "fire on this calendar date" 的合同,但 API 就是它的意思。

有了这些知识,您可以凭直觉知道您所看到的行为是预期的:如果您设置过去的日期,您实际上是在要求计时器从(负数)开始倒计时,因此计时器立即识别它的触发条件已满足(剩余时间 <= 0),并在被 运行 循环唤醒后立即触发。

但即使没有这些知识,我也认为这种行为满足的期望多于它违背的期望。考虑:

The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of [tolerance]. -- NSTimer class ref.

如果您将计时器安排在未来很短的时间内,则 运行 循环可能不会检查计时器,直到它的计划时间实际过去之后。通过触发定时器,即使时间已经过去,定时器或多或少会在指定时间触发,从而满足程序员的期望。

此外,您想要的行为会在管理计时器方面引入一系列复杂问题。如果您创建一个带有未来触发日期的计时器,但在该触发日期过去之前不添加它会怎样?计时器应该触发还是不触发?当前的行为避免了该决定。

对您来说最简单的解决方案可能是在安排计时器之前检查开火日期,并且仅在开火日期满足您的条件时才将其添加到 运行 循环中:

let date = self.StringToDate(checkOutTime, IsExtendedCheckout: isExtendedCheckout)
let comparison = date.compare(NSDate())
guard comparison != .OrderedAscending else {
    print("date \(date) has already passed. Not scheduling timer.")
    return
}