在UIScrollView
滚动(或其派生类)时,似乎所有NSTimers
正在运行的程序都被暂停,直到滚动完成为止。
有办法解决这个问题吗?线程?优先设置?有什么事吗
在UIScrollView
滚动(或其派生类)时,似乎所有NSTimers
正在运行的程序都被暂停,直到滚动完成为止。
有办法解决这个问题吗?线程?优先设置?有什么事吗
Answers:
一个易于实现的解决方案是:
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)
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
作为第一个命令而不是进行调用Timer.scheduleTimer()
,因为scheduleTimer()
它将计时器添加到运行循环中,而下一个调用是另一个添加至相同运行循环但具有不同模式的调用。不要做两次相同的工作。
这是快速版本。
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
tl; dr runloop正在滚动,因此它无法处理更多事件-除非您手动设置计时器,以便它在runloop处理触摸事件时也能发生。或尝试其他解决方案并使用GCD
必须阅读任何iOS开发人员。许多事情最终都是通过RunLoop执行的。
源自Apple的文档。
运行循环非常像其名称听起来。这是您的线程进入并用于运行事件处理程序以响应传入事件的循环
由于在运行运行循环时会传递计时器和其他定期事件,因此规避该循环会破坏这些事件的传递。每当您通过进入循环并重复从应用程序请求事件来实现鼠标跟踪例程时,就会出现此行为的典型示例。因为您的代码是直接捕获事件,而不是让应用程序正常分配事件,所以活动计时器将无法触发,直到您的鼠标跟踪例程退出并将控制权返回给应用程序之后。
这种情况发生了很多次,而我们却没有注意到。我的意思是我们将计时器设置为在10:10:10:00触发,但是runloop正在执行一个事件,该事件持续到10:10:10:05,因此计时器被触发10:10:10:06
同样,如果运行循环在执行处理程序例程的中间触发计时器,则计时器将等到下一次通过运行循环调用其处理例程。如果运行循环根本没有运行,则计时器永远不会触发。
您可以将计时器配置为仅一次或重复生成事件。重复计时器会根据计划的触发时间(而不是实际的触发时间)自动重新计划自身。例如,如果计划将计时器在特定时间触发,然后每5秒触发一次,则即使实际触发时间被延迟,计划的触发时间也将始终落在原始的5秒时间间隔上。如果触发时间延迟得太多,以至于错过了一个或多个计划的触发时间,则计时器将在错过的时间段内仅触发一次。在错过了一段时间后触发后,计时器将重新安排为下一个计划的触发时间。
你不能 操作系统只是为您自己进行更改。例如,当用户点击时,模式切换为eventTracking
。用户点击完成后,模式返回default
。如果您希望某些东西在特定模式下运行,则由您确定是否会发生。
当用户滚动时,运行循环模式变为tracking
。RunLoop设计用于变速。一旦将模式设置为eventTracking
,则它将优先考虑触摸事件(请记住我们的CPU内核数量有限)。这是OS设计师的体系结构设计。
默认情况下,计时器不在该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
}
对于重复计时器,请使用:
从与Daniel Jalkut的讨论中深入探讨:
问题:如何在RunLoop外部执行GCD(后台线程),例如后台线程上的asyncAfter?我的理解是,一切都将在RunLoop中执行
不一定-每个线程最多具有一个运行循环,但如果没有理由协调线程的执行“所有权”,则每个线程可以具有零。
线程是操作系统级别的功能,使您的进程能够在多个并行执行上下文中拆分其功能。运行循环是框架级别的功能,允许您进一步拆分单个线程,以便可以由多个代码路径有效地共享它。
通常,如果您调度要在线程上运行的内容,则除非有[NSRunLoop currentRunLoop]
隐式创建线程的调用,否则它可能不会有运行循环。
简而言之,模式基本上是输入和计时器的过滤机制