当使用新的组合框架时,可以指定从发布服务器接收元素的调度程序。
当将publisher分配给UI元素时,RunLoop.main和DispatchQueue.main之间有很大的区别吗?第一个返回主线程的run循环,以及与主线程关联的第二个队列。
发布于 2019-07-06 03:40:53
我在Swift论坛上发布了类似的问题。我鼓励大家看看https://forums.swift.org/t/runloop-main-or-dispatchqueue-main-when-using-combine-scheduler/26635的讨论。
我只是复制并粘贴来自Philippe_Hausler的答案
RunLoop.main作为调度器,最终调用RunLoop.main.perform,而DispatchQueue.main调用DispatchQueue.main.async来工作,实际上它们几乎是同构的。唯一真正的区别是,RunLoop调用最终在RunLoop标注中的不同位置执行,而如果lib分派中的优化启动,DispatchQueue变量可能会立即执行。在现实中,你永远不应该看到两者之间的区别。 RunLoop应该是当您有一个运行RunLoop的专用线程时,DispatchQueue可以是任何队列场景(为了记录,请避免在DispatchQueues中运行RunLoops,它会导致一些非常糟糕的资源使用.)。此外,值得注意的是,用于调度程序的DispatchQueue必须始终是串行的,才能遵守Combine的运算符的约定。
发布于 2020-04-08 18:41:02
实际上,使用RunLoop.main作为Scheduler与使用DispatchQueue.main作为Scheduler是有很大区别的。
RunLoop.main只在主运行循环在.default模式下运行时才运行回调,这是而不是--跟踪触摸和鼠标事件时使用的模式。如果将RunLoop.main用作Scheduler,则当用户处于触摸或拖动过程中时,事件将不会被传递给。DispatchQueue.main在所有.common模式中运行回调,其中包括跟踪触摸和鼠标事件时使用的模式。如果使用DispatchQueue.main,则事件将传递给,而使用用户则在触摸或拖动的中间。详细信息
我们可以看到RunLoop在Schedulers+RunLoop.swift中对Scheduler的一致性的实现。
public func schedule(options: SchedulerOptions?, \_ action: @escaping () -> Void) { self.perform(action) }
这使用了RunLoop perform(_:)方法,这是Objective方法-[NSRunLoop performBlock:]。performBlock:方法将块调度为只在默认运行循环模式下运行。(这没有记录在案。)
UIKit和AppKit在空闲时以默认模式运行run循环。但是,特别是在跟踪用户交互时(例如按下触摸或鼠标按钮),它们在不同的非默认模式下运行run循环。因此,使用receive(on: RunLoop.main) 的组合管道不会在用户触摸或拖动时传递信号。
我们可以看到DispatchQueue在Schedulers+DispatchQueue.swift中符合Scheduler的实现。
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方法异步(组:qos:标志:execute:)将块添加到队列中。在什么情况下执行主队列上的块?在普通的UIKit或AppKit应用程序中,主运行循环负责排空主队列。我们可以在CFRunLoop.c中找到run循环实现。重要的函数是__CFRunLoopRun,它太大了,不能全部引用。这里是兴趣线
#if __HAS_DISPATCH__ __CFPort dispatchPort = CFPORT_NULL;Boolean = pthread_main_np() && ( (HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) \x (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY &0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ );如果( libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes,rlm->_name) dispatchPort = _dispatch_get_main_queue_port_4CF();#endif
(为了提高可读性,我已经包装了原始源代码行。)这段代码所做的是:如果释放主队列是安全的,它是主运行循环,并且是.common模式,那么CFRunLoopRun将检查主队列是否准备就绪。否则,它将不进行检查,因此不会耗尽主队列。
.common模式包括跟踪模式。因此,使用receive(on: DispatchQueue.main) 的组合管道将在用户触摸或拖动时传递信号。
发布于 2019-11-14 03:39:05
我看到罗伊的回复,认为我可以互换使用,但实际上我注意到我的应用程序有很大的不同。
我正在自定义表视图单元格中异步加载一个图像。只要表视图正在滚动,使用RunLoop.main就会阻止图像的加载。
subscriber = NetworkController.fetchImage(url: searchResult.artworkURL)
.receive(on: RunLoop.main)
.replaceError(with: #imageLiteral(resourceName: "PlaceholderArtwork"))
.assign(to: \.image, on: artworkImageView)但是切换到DispatchQueue.main允许在滚动时加载图像。
subscriber = NetworkController.fetchImage(url: searchResult.artworkURL)
.receive(on: DispatchQueue.main)
.replaceError(with: #imageLiteral(resourceName: "PlaceholderArtwork"))
.assign(to: \.image, on: artworkImageView)https://stackoverflow.com/questions/56724566
复制相似问题