有没有一种方法可以指定运行循环和方式来接收发布者的元素

时间:2019-11-27 01:01:56

标签: swift uikit combine runloop

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

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

组合API:我认为receive(on:options:)方法have any matching options不能提供此功能。我在内部认为,如果我呼叫receive(on:RunLoop.main),则会呼叫RunLoop.main.perform { }。此perform方法可以使用the mode as parameter,但是未对Combine API公开。


当前想法:要解决此问题,我可以自己执行perform操作,而不使用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?

    }
}

2 个答案:

答案 0 :(得分:2)

实际上,您所请求的是自定义Scheduler,因为RunLoop是Scheduler,并且以特定模式而不是.default运行它,只是该调度程序的附加配置。 / p>

我认为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
}

答案 1 :(得分:0)

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

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

类似以下内容:

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