首页 > 解决方案 > RunLoop 与 DispatchQueue 作为调度程序

问题描述

使用新的组合框架时,您可以指定从发布者接收元素的调度程序。

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

标签: swiftgrand-central-dispatchnsrunloopcombine

解决方案


RunLoop.main使用as aScheduler和 using DispatchQueue.mainas a之间实际上有很大区别Scheduler

  • RunLoop.main仅当主运行循环在该.default模式下运行时才运行回调,这不是跟踪触摸和鼠标事件时使用的模式。如果您使用RunLoop.mainas a ,当用户处于触摸或拖动过程中时Scheduler,您的事件将不会被传递。

  • DispatchQueue.main在所有模式下运行回调.common,包括跟踪触摸和鼠标事件时使用的模式。如果您使用DispatchQueue.main,您的事件将在用户处于触摸或拖动过程中时传递。

细节

我们可以看到RunLoop's 的实现符合Schedulerin 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 一致性的实现。以下是它的实现方式: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)
        }
    }

因此,使用标准 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) 在用户触摸或拖动时传递信号。


推荐阅读