循环10次后延迟操作重复触发,求原因及解决方法
问题原因分析与解决方案
这个问题的核心是浮点数精度丢失导致多个闭包被安排在了同一个时间点执行,所以会出现批量打印的情况。
为什么会出现批量打印?
你在delay函数里的时间计算逻辑存在精度陷阱:
DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
这里把delay乘以纳秒数后转成Int64,会强制截断浮点数的小数部分。当timer累加到一定值时(比如5.5、10.5这类),浮点数的精度误差会让delay * Double(NSEC_PER_SEC)的实际值略小于真实的整数,转成Int64后就会少算几纳秒,导致多个循环的闭包最终得到的deadline完全相同——系统会一次性执行这些闭包,看起来就是一次打印多个数字。
另外,你用timer += 0.5累加延迟时间,Double类型的累加本身也会产生精度偏差(比如多次累加0.5后,值可能不是精确的整数倍数),这会进一步加剧时间点的重合。
解决方案
这里有几种简单有效的修复方式:
方案1:简化DispatchTime计算,避开浮点数截断
直接用DispatchTime.now() + delay即可,因为DispatchTime原生支持和Double类型的秒数相加,系统会帮你处理精度问题:
func delay(_ delay: Double, closure: @escaping ()->()) { DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure) }
方案2:根据循环索引直接计算延迟,避免累加误差
不用维护timer变量,直接用循环的i计算每次的延迟时间,这样每个闭包的延迟都是精确的0.5 * i秒:
for i in 1...30 { delay(0.5 * Double(i)) { print("Button \(self.newLevel[i-1].areaNumber) was lit up") } }
方案3:用整数累加纳秒,彻底规避浮点数问题
如果想手动控制纳秒级精度,可以用DispatchTimeInterval累加,完全避开浮点数运算:
var delayNanoseconds = 500_000_000 // 0.5秒对应的纳秒数 for i in 1...30 { DispatchQueue.main.asyncAfter(deadline: .now() + .nanoseconds(delayNanoseconds)) { print("Button \(self.newLevel[i-1].areaNumber) was lit up") } delayNanoseconds += 500_000_000 }
验证效果
用上面任意一种方案修改后,30次打印都会严格按照0.5秒的间隔执行,不会再出现批量打印的情况。
内容的提问来源于stack exchange,提问作者Rosstythesnowman




