English 中文(简体)
UIScrollView在滚动结束之前暂停NSTimer。
原标题:
  • 时间:2009-03-03 03:40:54
  •  标签:

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

有什么方法可以绕开这个问题吗?线程?优先级设置?任何东西?

最佳回答

一个易于实现的简单解决方案是:

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

对于任何使用Swift 3的人

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)

tl;dr 运行循环正在处理与滚动有关的事件。它无法处理更多的事件——除非您手动更改计时器的配置,以便在运行循环处理触摸事件时可以处理计时器。或者尝试替代解决方案并使用 GCD。


对于任何iOS开发者来说都是必读文章。许多事情最终都是通过RunLoop执行的。

源自于苹果文档

What is a Run Loop?

A run loop is very much like its name sounds. It is a loop your thread enters and uses to run event handlers in response to incoming events

How delivery of events are disrupted?

Because timers and other periodic events are delivered when you run the run loop, circumventing that loop disrupts the delivery of those events. The typical example of this behavior occurs whenever you implement a mouse-tracking routine by entering a loop and repeatedly requesting events from the application. Because your code is grabbing events directly, rather than letting the application dispatch those events normally, active timers would be unable to fire until after your mouse-tracking routine exited and returned control to the application.

What happens if timer is fired when run loop is in the middle of execution?

这种情况经常发生,而我们从未注意到。我的意思是我们设置了定时器在10:10:10:00触发,但是运行循环正在执行一个事件,需要到10:10:10:05,因此定时器会在10:10:10:06触发。

Similarly, if a timer fires when the run loop is in the middle of executing a handler routine, the timer waits until the next time through the run loop to invoke its handler routine. If the run loop is not running at all, the timer never fires.

Would scrolling or anything that keeps the runloop busy shift all the times my timer is going to fire?

You can configure timers to generate events only once or repeatedly. A repeating timer reschedules itself automatically based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so much that it misses one or more of the scheduled firing times, the timer is fired only once for the missed time period. After firing for the missed period, the timer is rescheduled for the next scheduled firing time.

How can I change the RunLoops s mode?

你不能。操作系统会自动更改模式。例如,当用户点击时,模式会切换到eventTracking。当用户点击完成后,模式将返回default。如果你想让某些事情在特定模式下运行,那么你需要确保它发生。


Solution:

当用户滚动时,运行循环模式变为tracking。RunLoop旨在切换档位。一旦模式设置为eventTracking,则它会优先处理触摸事件(请记住我们有限的CPU核心)。这是操作系统设计师的架构设计。

默认情况下,计时器不会在“跟踪”模式下安排。它们会安排在:

Creates a timer and schedules it on the current run loop in the default mode.

下面的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)

Ultimately doing one of the above means your thread is not blocked by touch events. which is equivalent to:

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.

Alternative solution:

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

不重复,只需使用:

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

对于重复定时器,请使用:

请看如何使用DispatchSourceTimer


从我与丹尼尔·雅尔库特的讨论中深入挖掘:

问题:GCD(后台线程)例如在后台线程上的 asyncAfter 如何在 RunLoop 之外执行? 我的理解是所有内容都应在 RunLoop 内执行。

不一定 - 每个线程最多只有一个运行循环,但如果没有协调执行线程的“所有权”原因,可能会为零。

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

通常情况下,如果你将某些东西分派到一个线程上运行,它可能没有一个运行循环,除非有什么东西调用[NSRunLoop currentRunLoop],这会隐式地创建一个运行循环。

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

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

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

如果你想在滚动时触发定时器,你必须运行另一个线程和另一个运行循环;由于定时器作为事件循环的一部分进行处理,如果你正在忙于处理视图滚动,就无法处理定时器。尽管在其他线程上运行定时器可能会带来性能/电池损耗,但可能不值得处理这种情况。

这是Swift版本。

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

适用于任何人使用Swift 4:

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

在 Swift 5 中测试。

var myTimer: Timer?

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




相关问题
热门标签