swift - RunLoop 与 DispatchQueue 作为调度程序
问题描述
使用新的组合框架时,您可以指定从发布者接收元素的调度程序。
在将发布者分配给 UI 元素时,RunLoop.main
和在这种情况下是否有很大区别?DispatchQueue.main
第一个返回主线程的运行循环和与主线程关联的第二个队列。
解决方案
RunLoop.main
使用as aScheduler
和 using DispatchQueue.main
as a之间实际上有很大区别Scheduler
:
RunLoop.main
仅当主运行循环在该.default
模式下运行时才运行回调,这不是跟踪触摸和鼠标事件时使用的模式。如果您使用RunLoop.main
as a ,当用户处于触摸或拖动过程中时Scheduler
,您的事件将不会被传递。DispatchQueue.main
在所有模式下运行回调.common
,包括跟踪触摸和鼠标事件时使用的模式。如果您使用DispatchQueue.main
,您的事件将在用户处于触摸或拖动过程中时传递。
细节
我们可以看到RunLoop
's 的实现符合Scheduler
in Schedulers+RunLoop.swift
。特别是,它的实现方式schedule(options:_:)
如下:
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) { self.perform(action) }
这里使用RunLoop
perform(_:)
方法,也就是 Objective-C 方法-[NSRunLoop performBlock:]
。该performBlock:
方法将块调度为仅在默认运行循环模式下运行。(这没有记录。)
UIKit 和 AppKit 在空闲时以默认模式运行运行循环。但是,特别是在跟踪用户交互(如触摸或鼠标按钮按下)时,它们会以不同的非默认模式运行运行循环。因此,当用户触摸或拖动时,使用的组合管道receive(on: RunLoop.main)
不会传递信号。
我们可以在Schedulers+DispatchQueue.swift中看到DispatchQueue
's 一致性的实现。以下是它的实现方式:Scheduler
schedule(options:_:)
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) { let qos = options?.qos ?? .unspecified let flags = options?.flags ?? [] if let group = options?.group { // Distinguish on the group because it appears to not be a call-through like the others. This may need to be adjusted. self.async(group: group, qos: qos, flags: flags, execute: action) } else { self.async(qos: qos, flags: flags, execute: action) } }
因此,使用标准 GCD 方法async(group:qos:flags:execute:)将块添加到队列中。什么情况下会执行主队列上的块?在普通的 UIKit 或 AppKit 应用程序中,主运行循环负责排空主队列。我们可以在CFRunLoop.c
. 重要的功能是__CFRunLoopRun
,它太大了,无法完整引用。以下是感兴趣的行:
#if __HAS_DISPATCH__ __CFPort dispatchPort = CFPORT_NULL; Boolean libdispatchQSafe = pthread_main_np() && ( (HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)) ); if ( libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name) ) dispatchPort = _dispatch_get_main_queue_port_4CF(); #endif
(为了便于阅读,我已经包装了原始源代码行。)这是该代码的作用:如果可以安全地排空主队列,并且它是主运行循环,并且是一种.common
模式,那么CFRunLoopRun
将检查主队列是否准备好排空. 否则,它不会检查,因此不会耗尽主队列。
.common
模式包括跟踪模式。因此,使用的组合管道receive(on: DispatchQueue.main)
将在用户触摸或拖动时传递信号。
推荐阅读
- python - 如何创建 spark udf 以将浮点数插值到 INT 以及如何编写比我做的更好的逻辑
- c - 在 N 体模拟中进行较少计算时程序运行速度变慢?
- android - 重新打开/更改应用程序时移动 ImageView 的 Android 相对布局
- excel - 公式中使用的值类型错误
- python-3.x - 使用 count() 但无法获得正确的输出
- javascript - 如何动态包含具有多个对象实例的 Javascript
- swift - 如何自动调整 NSOutlineView 的第一列以占用所有可用空间
- cron - 如何改进我的 cron 命令以便删除必要的文件夹?
- c++ - 在 C++11 中,如何不将参数传递给线程?
- msbuild - 该 msdeploy.axd?site= 参数值来自哪里?