![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
RunLoop 是一个循环,用于协调特定线程中传入事件的接收和处理。
RunLoop 存在于每个线程中,但默认情况下,它处于待机模式并且不执行任何工作。
开发人员可以根据需要运行它,但它不会自动运行。为此,您需要编写代码。
首先,RunLoop 旨在管理传入任务的流程并在正确的时间执行它们。
这在使用 UI 时最为明显,例如使用 UIScrollView 时。
默认情况下,主 RunLoop 始终在应用程序中运行;它处理来自系统的消息并将其传输到应用程序。此类消息的示例可以是例如用户点击屏幕时的事件。
辅助线程需要自行确定是否需要一个RunLoop。如果您需要它,您必须自己配置和运行它。不建议默认运行 RunLoop,只有在我们需要与线程主动交互的情况下才需要它。
另外,应用程序中的所有计时器都是在 runloop 上执行的,因此如果您需要在应用程序中与它们交互,那么您肯定需要研究 runloop 的功能。
RunLoop 是一个循环,它有多种操作模式,可以帮助开发人员了解何时运行特定任务。
所以,RunLoop可以有以下几种模式:
Default
- 默认模式,流是免费的,可以在其中安全地执行大型操作。
Tracking
- 线程正忙于做一些重要的工作。此时,最好不要运行任何任务,或者至少运行一些小任务。
Initialization
- 此模式在流初始化期间执行一次。
EventReceive
- 这是接收系统事件的内部模式,通常不使用。
Common
- 是一种占位符模式,没有实际意义。
在主RunLoop上,这些模式会自动切换;开发人员可以使用它们来执行耗时的任务,以便用户不会注意到界面挂起。让我们看一个例子。
其他RunLoop中的执行周期管理并不是全自动的。您需要为线程编写代码,以便在适当的时间启动执行周期。此外,您需要对事件做出适当的响应并使用无限循环来确保执行周期不会停止。
我们有一个 UIScrollView,我们需要在主线程上执行一项大型任务,以便用户不会注意到任何事情。
我们可以按照通常的方式完成任务:
DispatchQueue.main.async { sleep(2) self.tableView.refreshControl?.endRefreshing() }
但结果会很糟糕。用户会注意到应用程序出现明显的延迟。
出现这种负面影响的原因是我们在主线程上运行任务,而没有关注当时正在发生的事情。
正因为如此,当用户与界面交互时,我们就开始执行我们的大任务。当然,这会导致用户看到界面挂起。
使用RunLoop机制可以避免这种情况。让我们使用它来实现相同的逻辑:
CFRunLoopPerformBlock(CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) { sleep(2) self.tableView.refreshControl?.endRefreshing() }
让我解释一下这里发生了什么。 CFRunLoopPerformBlock 函数添加通过 RunLoop 执行的代码。除了代码块本身之外,该函数还有 2 个重要参数。
第一个负责选择哪个 RunLoop 应该执行该函数。在此示例中,使用“main”。
第二个负责任务完成的模式。
共有三种可能的模式:
通用 - 结合了默认模式和跟踪模式。
使用上面的代码运行程序的结果将是:
当用户开始与用户界面(UI)交互时,主运行循环切换到“跟踪”模式并暂时挂起所有其他事件的处理,以确保界面的流畅性。一旦用户停止与界面交互,运行循环就会返回到其“默认”模式并继续执行我们的任务。
除了用户界面之外,循环还与计时器的功能密切相关。
应用程序中的任何计时器都在循环中运行,您在使用它们时需要格外小心,不要犯错误,特别是当它们负责支付处理等重要功能时。
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething }
默认情况下,计时器以默认模式启动,因此如果用户此时滚动表格,计时器可能会停止工作。这是因为循环当前处于跟踪模式。这就是示例中的代码可能无法正常工作的原因。您可以通过在普通模式下添加定时器来解决此问题。
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething } RunLoop.main.add(timer, forMode: .common)
此外,计时器可能不会在预期时间触发或根本不触发。这是因为 RunLoop 仅在每个周期开始时检查计时器。如果计时器在 RunLoop 经过此阶段后触发,则直到下一次迭代开始时我们才会知道它。同时,任务在RunLoop上运行的时间越长,延迟也会越长。
为了解决这个问题,你可以创建一个新线程,在该线程中启动一个RunLoop,然后向该线程添加一个计时器,而不添加任何其他任务——这样,计时器就会正确运行。
let thread = Thread { let timer = Timer(timeInterval: 1.0, repeats: true) { timer in // makeSomething } RunLoop.current.add(timer, forMode: .default) RunLoop.current.run() } thread.start()
在本文中,我们了解了 RunLoop 是什么以及它在 iOS 应用程序中解决了哪些问题。 RunLoop 是一个循环,用于协调特定线程内传入事件的接收和处理,并且它具有多种操作模式。
它在使用用户界面 (UI) 和计时器时特别有用,因为它能够在正确的时间执行任务,这使您可以避免“挂起”界面并确保计时器的正确操作。
尽管使用 Run Loop 需要额外的编码,但这是一项值得的投资,可以提高应用程序的效率和稳定性。