RxSwift,为什么不使用 .never() 进行测试
RxSwift, why using .never() not for testing
我正在学习教程:
https://marcosantadev.com/mvvmc-with-swift/
里面讲的是MVVM-C
设计模式。我很难理解如何以及为什么在那里使用 .never()
observable(以及通常为什么我们想要使用 .never()
除了测试超时)。
任何人都可以给出 swift
代码中 .never()
可观察用法的合理示例(不是在测试中)并解释为什么它是必要的以及有哪些替代方案?
也许您的 ViewModel 有不同的配置(或者您在同一协议下有不同的 viewModel),其中一个不需要向其观察者发送任何更新。您可能希望能够将可观察对象定义为 .never()
,而不是说对于这种特定情况不存在可观察对象(您将实现为可选)。这在我看来更干净。
免责声明 - 我不是 RxSwift 的用户,但我假设 never
与 ReactiveSwift 类似,即从不发送任何值的信号。
我解决了从 View 到 ViewModel 的所有操作。用户点击按钮?很好,信号被传送到 ViewModel。这就是为什么我在 ViewModel 中有多个输入 observable。所有的可观察量都是optional
。它们是 optional
因为有时我编写测试并且真的不想提供所有虚假的可观察对象来测试某个单一功能。因此,我将其他可观察值提供为 nil
。但是使用 nil
不是很方便,所以我为所有 optional
observables 提供了一些默认行为,如下所示:
private extension ViewModel {
func observableNavigation() -> Observable<Navigation.Button> {
return viewOutputFactory().observableNavigation ?? Observable.never()
}
func observableViewState() -> Observable<ViewState> {
return viewOutputFactory().observableViewState ?? Observable.just(.didAppear)
}
}
如您所见,如果我将 nil
传递给 observableViewState
,我会将其替换为 just(.didAppear)
,因为 ViewModel 逻辑在很大程度上取决于视图状态。另一方面,如果我为 observableNavigation
传递 nil
,我会提供 never()
,因为我假设不会触发导航按钮。
但这整个故事只是我的观点。我打赌你会找到自己的地方来使用这个 never 运算符。
这是一个开放式问题,可以有很多答案,但我发现自己在很多情况下都找不到答案。有很多方法可以解决问题,但最近,我正在简化一些具有级联故障转移的设备连接代码,我想确定我上次扫描设备的尝试是否产生了任何结果。
为此,我想创建一个 Observable,它只在未看到任何结果就被处理的情况下发出“无扫描结果”事件,反之,如果看到,则不发出任何结果。
为了简洁起见,我从我的代码中删除了其他细节,但本质上:
func connect(scanDuration: TimeInterval) -> Observable<ConnectionEvent> {
let scan = scan(for: scanDuration).share(replay: 1)
let connection: Observable<ConnectionEvent> =
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan)
.flatMapLatest { [retainedSelf = self] in retainedSelf.connect(to: [=10=]) }
let scanDetector = scan
.toArray() // <-- sum all results as an array for final count
.asObservable()
.flatMap { results -> Observable<ConnectionEvent> in
results.isEmpty // if no scan results
? Observable.just(.noDevicesAvailable) // emit event
: Observable.never() } // else, got results, no action needed
// fold source and stream detector into common observable
return Observable.from([
connection
.filter { [=10=].isConnected }
.flatMapLatest { [retained = self] event -> Observable<ConnectionEvent> in
retained.didDisconnect(peripheral: event.connectedPeripheral!.peripheral)
.startWith(event) },
scanDetector])
.switchLatest()
}
作为反例,我在输入时意识到,还有一种更简单的方法可以满足我的需求,那就是在我的 concat 中添加一个最终错误发射 observable,它会进行故障转移,直到它命中最后一个错误案例,所以我不需要后面的错误检测流。
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan,
hardFailureEmitNoScanResults())
也就是说,在很多情况下我们可能想要监听和过滤下游,而 concat 技术不可用。
我正在学习教程:
https://marcosantadev.com/mvvmc-with-swift/
里面讲的是MVVM-C
设计模式。我很难理解如何以及为什么在那里使用 .never()
observable(以及通常为什么我们想要使用 .never()
除了测试超时)。
任何人都可以给出 swift
代码中 .never()
可观察用法的合理示例(不是在测试中)并解释为什么它是必要的以及有哪些替代方案?
也许您的 ViewModel 有不同的配置(或者您在同一协议下有不同的 viewModel),其中一个不需要向其观察者发送任何更新。您可能希望能够将可观察对象定义为 .never()
,而不是说对于这种特定情况不存在可观察对象(您将实现为可选)。这在我看来更干净。
免责声明 - 我不是 RxSwift 的用户,但我假设 never
与 ReactiveSwift 类似,即从不发送任何值的信号。
我解决了从 View 到 ViewModel 的所有操作。用户点击按钮?很好,信号被传送到 ViewModel。这就是为什么我在 ViewModel 中有多个输入 observable。所有的可观察量都是optional
。它们是 optional
因为有时我编写测试并且真的不想提供所有虚假的可观察对象来测试某个单一功能。因此,我将其他可观察值提供为 nil
。但是使用 nil
不是很方便,所以我为所有 optional
observables 提供了一些默认行为,如下所示:
private extension ViewModel {
func observableNavigation() -> Observable<Navigation.Button> {
return viewOutputFactory().observableNavigation ?? Observable.never()
}
func observableViewState() -> Observable<ViewState> {
return viewOutputFactory().observableViewState ?? Observable.just(.didAppear)
}
}
如您所见,如果我将 nil
传递给 observableViewState
,我会将其替换为 just(.didAppear)
,因为 ViewModel 逻辑在很大程度上取决于视图状态。另一方面,如果我为 observableNavigation
传递 nil
,我会提供 never()
,因为我假设不会触发导航按钮。
但这整个故事只是我的观点。我打赌你会找到自己的地方来使用这个 never 运算符。
这是一个开放式问题,可以有很多答案,但我发现自己在很多情况下都找不到答案。有很多方法可以解决问题,但最近,我正在简化一些具有级联故障转移的设备连接代码,我想确定我上次扫描设备的尝试是否产生了任何结果。
为此,我想创建一个 Observable,它只在未看到任何结果就被处理的情况下发出“无扫描结果”事件,反之,如果看到,则不发出任何结果。
为了简洁起见,我从我的代码中删除了其他细节,但本质上:
func connect(scanDuration: TimeInterval) -> Observable<ConnectionEvent> {
let scan = scan(for: scanDuration).share(replay: 1)
let connection: Observable<ConnectionEvent> =
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan)
.flatMapLatest { [retainedSelf = self] in retainedSelf.connect(to: [=10=]) }
let scanDetector = scan
.toArray() // <-- sum all results as an array for final count
.asObservable()
.flatMap { results -> Observable<ConnectionEvent> in
results.isEmpty // if no scan results
? Observable.just(.noDevicesAvailable) // emit event
: Observable.never() } // else, got results, no action needed
// fold source and stream detector into common observable
return Observable.from([
connection
.filter { [=10=].isConnected }
.flatMapLatest { [retained = self] event -> Observable<ConnectionEvent> in
retained.didDisconnect(peripheral: event.connectedPeripheral!.peripheral)
.startWith(event) },
scanDetector])
.switchLatest()
}
作为反例,我在输入时意识到,还有一种更简单的方法可以满足我的需求,那就是在我的 concat 中添加一个最终错误发射 observable,它会进行故障转移,直到它命中最后一个错误案例,所以我不需要后面的错误检测流。
Observable.concat(Observable.from(restorables ?? []),
connectedPeripherals(),
scan,
hardFailureEmitNoScanResults())
也就是说,在很多情况下我们可能想要监听和过滤下游,而 concat 技术不可用。