UIScrollView 暂停 NSTimer 直到滚动完成
当 UIScrollView(或其派生类)滚动时,似乎所有正在运行的 NSTimers 都会暂停,直到滚动完成。
有办法解决这个问题吗? 线程? 优先级设置? 任何事物?
While a UIScrollView
(or a derived class thereof) is scrolling, it seems like all the NSTimers
that are running get paused until the scroll is finished.
Is there a way to get around this? Threads? A priority setting? Anything?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
如果您希望在滚动时触发计时器,则必须运行另一个线程和另一个运行循环; 由于计时器是作为事件循环的一部分进行处理的,因此如果您忙于处理滚动视图,则永远不会抽出时间来处理计时器。 尽管在其他线程上运行计时器的性能/电池损失可能不值得处理这种情况。
You have to run another thread and another run loop if you want timers to fire while scrolling; since timers are processed as part of the event loop, if you're busy processing scrolling your view, you never get around to the timers. Though the perf/battery penalty of running timers on other threads might not be worth handling this case.
这是快速版本。
This is the swift version.
对于任何使用 Swift 4 的人:
for anyone use Swift 4:
在 swift 5 中测试
Tested in swift 5
一个简单的& 实施简单的解决方案是:
An easy & simple to implement solution is to do:
对于任何使用 Swift 3 的人
For anyone using Swift 3
tl;dr 运行循环正在处理与滚动相关的事件。 它无法处理更多事件 - 除非您手动更改计时器的配置,以便在 runloop 处理触摸事件时可以处理计时器。 或者尝试替代解决方案并使用 GCD
任何 iOS 开发人员都必须阅读的内容。 很多事情最终都是通过RunLoop来执行的。
源自 Apple 文档。
什么是运行循环?
事件的传递如何被中断?
如果在运行循环执行过程中触发计时器会发生什么?
这种情况发生了很多次,而我们却没有注意到。 我的意思是我们将计时器设置为在 10:10:10:00 触发,但运行循环正在执行一个事件,该事件需要持续到 10:10:10:05,因此计时器在 10:10:10:06 触发
滚动或任何让运行循环忙碌的东西是否会在我的计时器即将触发时一直变化?
如何更改 RunLoops 的模式?
你不能。 操作系统只会为您改变自身。 例如,当用户点击时,模式切换到
eventTracking
。 当用户点击完成后,模式将返回到默认
。 如果您希望某些东西在特定模式下运行,那么您需要确保这种情况发生。解决方案:
当用户滚动时,运行循环模式变为
tracking< /代码>
。 RunLoop 旨在切换齿轮。 一旦模式设置为
eventTracking
,它就会优先处理触摸事件(记住我们的 CPU 核心有限)。 这是操作系统设计者的架构设计。默认情况下,计时器不会在
跟踪
模式下安排。 他们安排在:下面的
scheduledTimer
执行以下操作:如果您如果希望计时器在滚动时工作,那么您必须执行以下任一操作:
或者直接执行:
最终执行上述操作之一意味着您的线程不会被触摸事件阻塞。
这相当于:
替代解决方案:
您可以考虑使用 GCD 作为计时器,这将帮助您“屏蔽”代码免受运行循环管理问题的影响。
对于非重复只需使用:
对于重复计时器使用:
请参阅如何使用 DispatchSourceTimer
从我与 Daniel 的讨论中深入挖掘Jalkut:
问题:GCD(后台线程)(例如后台线程上的 asyncAfter)如何在 RunLoop 之外执行? 我对此的理解是,一切都将在 RunLoop 内执行,
不一定 - 每个线程最多有一个运行循环,但如果没有理由协调线程的执行“所有权”,则可以有零个运行循环。
线程是操作系统级别的可供性,使您的进程能够在多个并行执行上下文中分割其功能。 运行循环是一种框架级功能,允许您进一步拆分单个线程,以便多个代码路径可以有效地共享它。
通常,如果您分派在线程上运行的某些内容,它可能不会有运行循环,除非调用
[NSRunLoop currentRunLoop]
来隐式创建一个运行循环。简而言之,模式基本上是输入和计时器的过滤机制
tl;dr the runloop is handing scroll related events. It can't handle any more events — unless you manually change the timer's config so the timer can be processed while runloop is handling touch events. OR try an alternate solution and use GCD
A must read for any iOS developer. Lots of things are ultimately executed through RunLoop.
Derived from Apple's docs.
What is a Run Loop?
How delivery of events are disrupted?
What happens if timer is fired when run loop is in the middle of execution?
This happens A LOT OF TIMES, without us ever noticing. I mean we set the timer to fire at 10:10:10:00, but the runloop is executing an event which takes till 10:10:10:05, hence the timer is fired 10:10:10:06
Would scrolling or anything that keeps the runloop busy shift all the times my timer is going to fire?
How can I change the RunLoops's mode?
You can't. The OS just changes itself for you. e.g. when user taps, then the mode switches to
eventTracking
. When the user taps are finished, the mode goes back todefault
. If you want something to be run in a specific mode, then it's up to you make sure that happens.Solution:
When user is scrolling the the Run Loop Mode becomes
tracking
. The RunLoop is designed to shifts gears. Once the mode is set toeventTracking
, then it gives priority (remember we have limited CPU cores) to touch events. This is an architectural design by the OS designers.By default timers are NOT scheduled on the
tracking
mode. They are scheduled on:The
scheduledTimer
underneath does this:If you want your timer to work when scrolling then you must do either:
Or just do:
Ultimately doing one of the above means your thread is not blocked by touch events.
which is equivalent to:
Alternative solution:
You may consider using GCD for your timer which will help you to "shield" your code from run loop management issues.
For non-repeating just use:
For repeating timers use:
See how to use DispatchSourceTimer
Digging deeper from a discussion I had with Daniel Jalkut:
Question: how does GCD (background threads) e.g. a asyncAfter on a background thread get executed outside of the RunLoop? My understanding from this is that everything is to be executed within a RunLoop
Not necessarily - every thread has at most one run loop, but can have zero if there's no reason to coordinate execution "ownership" of the thread.
Threads are an OS level affordance that gives your process the ability to split up its functionality across multiple parallel execution contexts. Run loops are a framework-level affordance that allows you to further split up a single thread so it can be shared efficiently by multiple code paths.
Typically if you dispatch something that gets run on a thread, it probably won't have a runloop unless something calls
[NSRunLoop currentRunLoop]
which would implicitly create one.In a nutshell, modes are basically a filter mechanism for inputs and timers
是的,保罗是对的,这是一个运行循环问题。 具体来说,您需要使用 NSRunLoop 方法:
Yes, Paul is right, this is a run loop issue. Specifically, you need to make use of the NSRunLoop method: