RunLoop vs DispatchQueue作为调度程序

mik*_*098 5 grand-central-dispatch nsrunloop swift combine

使用新的Combine框架时,您可以指定用于从发布服务器接收元素的调度程序。

在将发布者分配给UI元素时RunLoop.mainDispatchQueue.main在这种情况下,两者之间有很大区别吗?第一个返回主线程的运行循环,第二个返回与主线程关联的队列。

rob*_*off 55

RunLoop.main用作 aSchedulerDispatchQueue.main用作 a之间实际上存在很大差异Scheduler

  • RunLoop.main仅当主运行循环在.default模式下运行时才运行回调,这不是跟踪触摸和鼠标事件时使用的模式。
  • DispatchQueue.main在所有.common模式下运行回调,包括跟踪触摸和鼠标事件时使用的模式。

细节

我们可以看到 的实现RunLoop符合Schedulerin Schedulers+RunLoop.swift。特别是,这是它的实现方式schedule(options:_:)

    public func schedule(options: SchedulerOptions?,
                         _ action: @escaping () -> Void) {
        self.perform(action)
    }
Run Code Online (Sandbox Code Playgroud)

这将使用RunLoop perform(_:)方法,即 Objective-C 方法-[NSRunLoop performBlock:]。该performBlock:方法将块调度为仅在默认运行循环模式运行。(这没有记录。)

UIKit 和 AppKit 在空闲时以默认模式运行 run loop。但是,特别是在跟踪用户交互(如触摸或鼠标按钮按下)时,它们以不同的非默认模式运行运行循环。因此,当用户触摸或拖动时,使用的组合管道receive(on: RunLoop.main) 不会传递信号。

我们可以在Schedulers+DispatchQueue.swift 中看到DispatchQueue的符合性的实现。这是它的实现方式:Schedulerschedule(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)
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,使用标准 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
Run Code Online (Sandbox Code Playgroud)

(为了可读性,我已经包装了原始源代码行。)这是该代码的作用:如果排空主队列是安全的,并且它是主运行循环,并且它是一种.common模式,那么CFRunLoopRun将检查主队列是否准备好排空. 否则,它不会检查,因此不会排空主队列。

.common模式包括跟踪模式。因此,使用的组合管道receive(on: DispatchQueue.main) 在用户触摸或拖动时传递信号。


lan*_*pps 24

我看到了 Roy 发布的回复,并认为我可以互换使用它们,但实际上我注意到我的应用程序有很大的不同。

我在自定义表格视图单元格中异步加载图像。RunLoop.main只要表格视图在滚动,使用就会阻止加载图像。

  subscriber = NetworkController.fetchImage(url: searchResult.artworkURL)
    .receive(on: RunLoop.main)
    .replaceError(with: #imageLiteral(resourceName: "PlaceholderArtwork"))
    .assign(to: \.image, on: artworkImageView)
Run Code Online (Sandbox Code Playgroud)

但是切换到DispatchQueue.main允许图像在滚动时加载。

  subscriber = NetworkController.fetchImage(url: searchResult.artworkURL)
    .receive(on: DispatchQueue.main)
    .replaceError(with: #imageLiteral(resourceName: "PlaceholderArtwork"))
    .assign(to: \.image, on: artworkImageView)
Run Code Online (Sandbox Code Playgroud)


小智 10

我已经在 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 标注中的不同位置执行,而 DispatchQueue 变体可能会在 libdispatch 中的优化开始时立即执行。实际上,您永远不应该真正看到两者之间的差异。

RunLoop 应该是当你有一个运行 RunLoop 的专用线程时,DispatchQueue 可以是任何队列场景(并且为了记录,请避免在 DispatchQueues 中运行 RunLoops,它会导致一些非常粗糙的资源使用......)。另外值得注意的是,用作调度程序的 DispatchQueue 必须始终是串行的,以遵守 Combine 的运营商的合同。