Timer.publish(every:on:in:)
。
wait(for:timeout:)
并能够同步测试所有内容。
TimerPublisher
发现它符合ConnectablePublisher
,为了抽象出这一点,我为假定时器和工厂制作了一些样板代码:
import Foundation
import Combine
protocol TimerFactory {
static func makeTimer() -> any ConnectablePublisher<Date, Never>
}
enum FakeTimerFactory {
static func makeTimer() -> any ConnectablePublisher<Date, Never> {
return FakeTimer()
}
}
struct FakeTimer: ConnectablePublisher {
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Date == S.Input {
}
typealias Output = Date
typealias Failure = Never
func connect() -> Cancellable {
return FakeCancellable()
}
struct FakeCancellable: Cancellable {
func cancel() {
}
}
}
但是,使用以下示例代码我无法在 autoconnect()
上调用
ConnectablePublisher
扩展方法:
FakeTimerFactory.makeTimer()
.autoconnect()
.sink { _ in
print("Tick!")
}
我收到以下错误:
Member 'autoconnect' cannot be used on value of type 'any ConnectablePublisher<Date, Never>'; consider using a generic constraint instead
作为参考,我希望此代码可以直接替代 Timer.publish(every:on:in:)
:
Timer.publish(every: 1.0, on: .main, in: .default)
.autoconnect()
.sink { _ in
print("Tick!")
}
我仍然需要弄清楚如何实现虚拟时间,但当前的错误似乎是一个存在类型问题。问题
autoconnect()
不可用?
TimerPublisher
?
RealTimerFactory
兼容,如下所示:
enum RealTimerFactory: TimerFactory {
static func makeTimer() -> any ConnectablePublisher<Date, Never> {
// these parameters are hard-coded for brevity
// ideally we would pass this in as `makeTimer(every:on:in:)`
return Timer.publish(every: 1.0, on: .main, in: .default)
}
}
这样我们就可以交换测试代码和生产代码之间的实现:
final class UsesTimers {
var timerFactory: TimerFactory.Type
init(timerFactory: TimerFactory.Type = RealTimerFactory.self) {
self.timerFactory = timerFactory
}
func doSomethingWithTimers() {
timerFactory.makeTimer()
.autoconnect() // Error occurs here
.sink { _ in
print("Tick")
}
}
}
autoconnect
不可用,因为其签名提到
Self
:
func autoconnect() -> Publishers.Autoconnect<Self>
对于存在类型,Self
在编译时是未知的。
AnyPublisher
。
struct AnyConnectablePublisher<Output, Failure: Error>: ConnectablePublisher {
let upstream: any ConnectablePublisher<Output, Failure>
func connect() -> any Cancellable {
upstream.connect()
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
upstream.receive(subscriber: subscriber)
}
init(connectable upstream: any ConnectablePublisher<Output, Failure>) {
self.upstream = upstream
}
init<Upstream>(publisher upstream: Upstream) where
Self.Failure == Never,
Upstream: Publisher,
Upstream.Output == Self.Output,
Upstream.Failure == Never
{
self.upstream = upstream.makeConnectable()
}
}
为了方便起见,我使用 makeConnectable
添加了一个额外的初始化程序来包装任何通用发布者。然后可以声明工厂协议的方法返回
AnyConnectablePublisher<Date, Never>
。在这里,我展示了一个示例,其中假计时器输出 1970 年的前 10 秒。
enum FakeTimerFactory: TimerFactory {
static func makeTimer() -> AnyConnectablePublisher<Date, Never> {
AnyConnectablePublisher(
publisher: (0..<10).publisher
.flatMap(maxPublishers: .max(1)) { i in
Just(Date(timeIntervalSince1970: Double(i)))
.delay(for: 1, scheduler: RunLoop.main)
}
)
}
}
实时工厂直接返回即可
AnyConnectablePublisher(connectable: Timer.publish(every: 1.0, on: .main, in: .default))
什么发布何时。我认为更方便的方法是直接注入 AnyConnectablePublisher
,而不是工厂。这样,您就可以注入一个包含
AnyConnectablePublisher
的
PassthroughSubject
。
let subject = PassthroughSubject<Date, Never>()
let timer = AnyConnectablePublisher(publisher: subject)
let thingToBeTested = ThingToBeTested(timer: timer)
// now you can control the publishing by calling `subject.send(someDate)`
或者您可以将 makeTimer
创建为instance 方法,并注入工厂的 instance,而不是元类型。然后
PassthroughSubject
就可以存在于工厂实例中。