UIScrollView 暂停 NSTimer 直到滚动完成

发布于 2024-07-14 08:45:23 字数 108 浏览 6 评论 0原文

当 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

妄断弥空 2024-07-21 08:45:24

如果您希望在滚动时触发计时器,则必须运行另一个线程和另一个运行循环; 由于计时器是作为事件循环的一部分进行处理的,因此如果您忙于处理滚动视图,则永远不会抽出时间来处理计时器。 尽管在其他线程上运行计时器的性能/电池损失可能不值得处理这种情况。

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.

凝望流年 2024-07-21 08:45:24

这是快速版本。

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

This is the swift version.

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
述情 2024-07-21 08:45:24

对于任何使用 Swift 4 的人:

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

for anyone use Swift 4:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)
过气美图社 2024-07-21 08:45:24

在 swift 5 中测试

var myTimer: Timer?

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

Tested in swift 5

var myTimer: Timer?

self.myTimer= Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
      //your code
}
    
RunLoop.main.add(self.myTimer!, forMode: .common)
渡你暖光 2024-07-21 08:45:23

一个简单的& 实施简单的解决方案是:

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

An easy & simple to implement solution is to do:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
萌︼了一个春 2024-07-21 08:45:23

对于任何使用 Swift 3 的人

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


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

For anyone using Swift 3

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


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
楠木可依 2024-07-21 08:45:23

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


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

源自 Apple 文档

什么是运行循环?

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

事件的传递如何被中断?

因为计时器和其他周期性事件是在您运行时传递的
运行循环,绕过该循环会破坏那些的交付
事件。 这种行为的典型例子发生在当你
通过进入循环并重复执行鼠标跟踪例程
从应用程序请求事件。 因为你的代码正在抓取
直接事件,而不是让应用程序调度这些事件
通常情况下,活动计时器将无法触发,直到
您的鼠标跟踪例程退出并将控制权返回给
应用程序。

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

这种情况发生了很多次,而我们却没有注意到。 我的意思是我们将计时器设置为在 10:10:10:00 触发,但运行循环正在执行一个事件,该事件需要持续到 10:10:10:05,因此计时器在 10:10:10:06 触发

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

滚动或任何让运行循环忙碌的东西是否会在我的计时器即将触发时一直变化?

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

如何更改 RunLoops 的模式?

你不能。 操作系统只会为您改变自身。 例如,当用户点击时,模式切换到eventTracking。 当用户点击完成后,模式将返回到默认。 如果您希望某些东西在特定模式下运行,那么您需要确保这种情况发生。


解决方案:

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

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

创建一个计时器并将其调度到当前运行循环中
默认模式。

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

对于重复计时器使用:

请参阅如何使用 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?

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?

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

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?

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 to default. 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 to eventTracking, 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:

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

The scheduledTimer underneath does this:

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

If you want your timer to work when scrolling then you must do either:

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

Or just do:

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:

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:

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

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

2024-07-21 08:45:23

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

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

Yes, Paul is right, this is a run loop issue. Specifically, you need to make use of the NSRunLoop method:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文