有没有办法指定 运行 循环和模式以从发布者接收元素

Is there a way to specify the run loop & mode to to receive elements from a publisher

我可以将调度程序指定为 RunLoop.main,但我找不到提供关联 RunLoop.Mode 模式以从发布者接收元素的本机方法。

我为什么需要这个: 我正在更新我的发布者的 tableView 单元格,但是如果用户滚动,UI 不会更新,然后更新一旦用户交互或滚动停止。 This is a known behaviour for scrollViews 但我希望尽快显示我的内容,能够指定 运行 循环跟踪模式将解决此问题。

Combine API:我不认为receive(on:options:)方法have any matching options to provide this. I think internally, if I call receive(on:RunLoop.main) then RunLoop.main.perform { } is called. This perform method can take the mode as parameter但是这个没有接触到CombineAPI.


当前想法:要解决这个问题,我可以自己执行执行操作而不使用 Combine API,所以不要这样做:

cancellable = stringFuture.receive(on: RunLoop.main) // I cannot specify the mode here
                          .sink { string in
    cell.textLabel.text = string
}

我能做到:

cancellable = stringFuture.sink { string in
    RunLoop.main.perform(inModes: [RunLoop.Mode.common]) { // I can specify it here
        cell.textLabel.text = string
    }
}

但这并不理想。

理想的解决方案: 我想知道如何将其包装到我自己的发布者函数实现中以具有如下内容:

cancellable = stringFuture.receive(on: RunLoop.main, inMode: RunLoop.Mode.common)
                          .sink { string in
    cell.textLabel.text = string
}

如果这个函数的 API 可以是这样的:

extension Publisher {
    public func receive(on runLoop: RunLoop, inMode: RunLoop.Mode) -> AnyPublisher<Future.Output, Future.Failure> {

        // How to implement this?

    }
}

实际上你要求的是自定义 Scheduler,因为 RunLoop 是 Scheduler 并且 运行 它在特定模式下,而不是 .default,只是额外的该调度程序的配置。

我认为 Apple 会在他们的 RunLoop 调度程序中的一些下一个更新中添加这种可能性,但现在以下简单的自定义调度程序包装 RunLoop 适合我。希望对您有所帮助。

用法:

.receive(on: MyScheduler(runLoop: RunLoop.main, modes: [RunLoop.Mode(rawValue: "myMode")]))

.delay(for: 10.0, scheduler: MyScheduler(runLoop: RunLoop.main, modes: [.common]))

调度程序代码:

struct MyScheduler: Scheduler {
    var runLoop: RunLoop
    var modes: [RunLoop.Mode] = [.default]

    func schedule(after date: RunLoop.SchedulerTimeType, interval: RunLoop.SchedulerTimeType.Stride,
                    tolerance: RunLoop.SchedulerTimeType.Stride, options: Never?,
                    _ action: @escaping () -> Void) -> Cancellable {
        let timer = Timer(fire: date.date, interval: interval.magnitude, repeats: true) { timer in
            action()
        }
        for mode in modes {
            runLoop.add(timer, forMode: mode)
        }
        return AnyCancellable {
            timer.invalidate()
        }
    }

    func schedule(after date: RunLoop.SchedulerTimeType, tolerance: RunLoop.SchedulerTimeType.Stride,
                    options: Never?, _ action: @escaping () -> Void) {
        let timer = Timer(fire: date.date, interval: 0, repeats: false) { timer in
            timer.invalidate()
            action()
        }
        for mode in modes {
            runLoop.add(timer, forMode: mode)
        }
    }

    func schedule(options: Never?, _ action: @escaping () -> Void) {
        runLoop.perform(inModes: modes, block: action)
    }

    var now: RunLoop.SchedulerTimeType { RunLoop.SchedulerTimeType(Date()) }
    var minimumTolerance: RunLoop.SchedulerTimeType.Stride { RunLoop.SchedulerTimeType.Stride(0.1) }

    typealias SchedulerTimeType = RunLoop.SchedulerTimeType
    typealias SchedulerOptions = Never
}

您可以将 DispatchQueue.main 传递给 receive(on:options:),因为 DispatchQueue 也符合 Scheduler 协议。

它以某种方式在滚动时传递事件。

喜欢以下内容:

cancellable = stringFuture.receive(on: DispatchQueue.main)
                          .sink { string in
    cell.textLabel.text = string
}