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

Lud*_*dry 5 uikit runloop swift combine

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

为什么我需要这个:我正在更新我的发布者的 tableView 单元格,但如果用户正在滚动,UI 不会更新,然后在用户交互或滚动停止后立即更新。这是滚动视图的已知行为,但我希望我的内容尽快显示,并且能够指定运行循环跟踪模式可以解决此问题。

组合 API:我认为该receive(on:options:)方法没有任何匹配选项来提供此功能。我认为在内部,如果我打电话,receive(on:RunLoop.main)那么RunLoop.main.perform { }就会被调用。此执行方法可以将模式作为参数,但这不会暴露给组合 API。


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

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

我可以这样做:

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

但这并不理想。

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

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

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

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

        // How to implement this?

    }
}
Run Code Online (Sandbox Code Playgroud)

Asp*_*eri 4

实际上,您所要求的是自定义Scheduler,因为 RunLoop 是一个Scheduler并在特定模式下运行它,而不是.default,只是该调度程序的附加配置。

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

用法:

.receive(on: MyScheduler(runLoop: RunLoop.main, modes: [RunLoop.Mode(rawValue: "myMode")]))
Run Code Online (Sandbox Code Playgroud)

或者

.delay(for: 10.0, scheduler: MyScheduler(runLoop: RunLoop.main, modes: [.common]))
Run Code Online (Sandbox Code Playgroud)

调度程序代码:

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
}
Run Code Online (Sandbox Code Playgroud)