UIScrollView暂停NSTimer直到滚动完成

时间:2009-03-03 03:40:54

标签: ios uiscrollview nstimer

UIScrollView(或其派生类)滚动时,似乎所有正在运行的NSTimers都会暂停,直到滚动完成。

有没有办法解决这个问题?主题?优先级设置?什么?

8 个答案:

答案 0 :(得分:195)

简单易行简单的实施解决方案是:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

答案 1 :(得分:22)

对于任何使用Swift 3的人

{{1}}

答案 2 :(得分:8)

是的,Paul是对的,这是一个运行循环问题。具体来说,您需要使用NSRunLoop方法:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

答案 3 :(得分:7)

这是快速版本。

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

答案 4 :(得分:5)

如果你想在滚动时触发定时器,你必须运行另一个线程和另一个运行循环;由于计时器是作为事件循环的一部分处理的,如果您正在忙于处理滚动视图,那么您永远不会到达计时器。虽然在其他线程上运行计时器的性能/电池损失可能不值得处理这种情况。

答案 5 :(得分:2)

tl; dr,runloop正在滚动,因此它无法处理更多事件-除非您手动设置计时器,以便它在runloop处理触摸事件时也可以发生。或尝试其他解决方案并使用GCD


任何iOS开发人员都必须阅读。许多事情最终都是通过RunLoop执行的。

源自Apple's docs

什么是运行循环?

运行循环很像它的名字听起来。这是一个循环你的线程 输入并用于运行事件处理程序以响应传入事件

如何中断事件的发送?

因为在运行时传递了计时器和其他定期事件 运行循环,规避循环会破坏这些循环的传递 事件。每当您出现此行为的典型示例 通过进入循环并重复执行鼠标跟踪例程 从应用程序请求事件。因为您的代码正在抓取 事件,而不是让应用程序分派事件 通常情况下,活动计时器要等到之后才能触发 您的鼠标跟踪例程退出,并将控制权返回给 应用。

如果在执行过程中运行循环时触发计时器会发生什么?

这种情况经常发生,而我们却没有注意到。我的意思是我们将计时器设置为在10:10:10:00触发,但是runloop正在执行一个事件,该事件持续到10:10:10:05,因此计时器被触发10:10:10:06

类似地,如果定时器在运行循环中间时触发 执行一个处理程序例程,计时器等待直到下一次 通过运行循环调用其处理程序例程。如果运行循环为 根本不运行,计时器永远不会触发。

在我的计时器启动时,是否会滚动或使runloop忙于移位?

您可以将计时器配置为仅一次或重复生成事件。一种 重复计时器会根据 预定的点火时间,而不是实际的点火时间。例如,如果 计时器计划在特定时间每5秒触发一次 之后,预定的触发时间将始终位于原始触发时间 即使实际触发时间被延迟,也要间隔5秒。 如果射击时间延迟太多,以至于错过了一个或多个 在预定的触发时间中,计时器仅触发一次 错过的时间段。触发错过的时间后,计时器为 重新安排了下一个计划的点火时间。

如何更改RunLoops的模式?

不能。操作系统只是为您自己进行更改。例如当用户点击时,模式将切换为eventTracking。用户轻按完成后,模式将返回到default。如果您希望某些东西在特定模式下运行,则取决于您确保这种情况发生。


解决方案:

当用户滚动时,运行循环模式变为tracking。 RunLoop设计用于变速。将模式设置为eventTracking后,它将为触摸事件赋予优先级(请记住我们的CPU内核数量有限)。 This is an architectural design by the OS designers

默认情况下,没有在tracking模式下安排计时器。他们安排在:

创建一个计时器并将其安排在当前运行循环中 默认模式。

下面的scheduledTimer执行此操作:

RunLoop.main.add(timer, forMode: .default)

如果您希望定时器在滚动时起作用,那么您必须执行以下任一操作:

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
 selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode

RunLoop.main.add(timer, forMode: .tracking) // AND Do this

或者只是做:

RunLoop.main.add(timer, forMode: .common)

最终执行上述操作之一意味着您的线程被触摸事件阻塞。 等效于:

RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.

替代解决方案:

您可以考虑将GCD用作计时器,这将帮助您“屏蔽”代码免受运行循环管理问题的影响。

对于非重复使用:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    // your code here
}

要重复使用计时器,请使用:

请参见how to use DispatchSourceTimer


深入探讨与Daniel Jalkut的讨论:

问题:GCD(后台线程)例如在RunLoop外部执行后台线程上的asyncAfter吗?我的理解是,一切都将在RunLoop中执行

不一定-每个线程最多具有一个运行循环,但如果没有理由协调线程的执行“所有权”,则可以为零。

线程是操作系统级别的功能,使您的进程能够在多个并行执行上下文中拆分其功能。运行循环是框架级别的功能,可让您进一步拆分单个线程,以便可以由多个代码路径有效地共享它。

通常,如果您调度要在线程上运行的对象,则除非有调用[NSRunLoop currentRunLoop]的对象隐式创建一个线程,否则它可能不会有运行循环。

简而言之,模式基本上是用于输入和计时器的过滤机制

答案 6 :(得分:1)

任何使用Swift 4的人:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)

答案 7 :(得分:0)

在 swift 5 中测试

var myTimer: Timer?

self.myTimer= Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
      //your code
}
    
RunLoop.main.add(self.myTimer!, forMode: .common)