为什么完成事件会阻止这个测试通过?
我知道有很多的代码在这里的SO问题,但它是我能在那一刻做了最好的......你可以复制/粘贴代码为一启动RX-操场上看到的问题。为什么完成事件会阻止这个测试通过?
在第89行有一段注释码let creds = Observable.just(credentials)//.concat(Observable.never())
。如果我删除//
并允许concat,代码将通过它的测试。如果creds
被允许发送完成事件,任何人都可以提供一个线索,说明为什么这段代码不能通过测试?
import Foundation
import RxSwift
import RxCocoa
import UIKit
typealias Credentials = (email: String, password: String)
struct User {
let id: String
let properties: [Property]
}
struct Property {
let id: String
let name: String
}
struct LoginParams {
let touchIDPossible: Bool
}
class LoginScreen {
var attemptLogin: Observable<Credentials> {
assert(_attemptLogin == nil)
_attemptLogin = PublishSubject()
return _attemptLogin!
}
var _attemptLogin: PublishSubject<(email: String, password: String)>?
}
class DashboardScreen {
func display(property: Observable<Property?>) {
property.subscribe(onNext: { [unowned self] in
self._property = $0
}).disposed(by: bag)
}
var _property: Property?
let bag = DisposeBag()
}
class Interface {
func login(params: LoginParams) -> Observable<LoginScreen> {
assert(_login == nil)
_login = PublishSubject()
return _login!
}
func dashboard() -> Observable<DashboardScreen> {
assert(_dashboard == nil)
_dashboard = PublishSubject()
return _dashboard!
}
var _login: PublishSubject<LoginScreen>?
var _dashboard: PublishSubject<DashboardScreen>?
let bag = DisposeBag()
}
class Server {
func user(credentials: Credentials) -> Observable<User> {
assert(_user == nil)
_user = PublishSubject()
return _user!
}
func property(id: String) -> Observable<Property> {
assert(_property == nil)
_property = PublishSubject()
return _property!
}
var _user: PublishSubject<User>?
var _property: PublishSubject<Property>?
}
class Coordinator {
init(interface: Interface, server: Server) {
self.interface = interface
self.server = server
}
func start() {
let credentials = (email: "foo", password: "bar")
// remove the `//` and the test will pass. Why does it fail when `creds` completes?
let creds = Observable.just(credentials)//.concat(Observable.never())
let autoUser = creds.flatMap {
self.server.user(credentials: $0)
.materialize()
.filter { !$0.isCompleted }
}.shareReplayLatestWhileConnected()
let login = autoUser.filter { $0.error != nil }
.flatMap { _ in self.interface.login(params: LoginParams(touchIDPossible: false)) }
let attempt = login.flatMap { $0.attemptLogin }
.shareReplayLatestWhileConnected()
let user = attempt.flatMap {
self.server.user(credentials: $0)
.materialize()
.filter { !$0.isCompleted }
}.shareReplayLatestWhileConnected()
let propertyID = Observable.merge(autoUser, user).map { $0.element }
.filter { $0 != nil }.map { $0! }
.map { $0.properties.sorted(by: { $0.name < $1.name }).map({ $0.id }).first }
let property = propertyID.filter { $0 != nil }.map { $0! }
.flatMap { self.server.property(id: $0)
.map { Optional.some($0) }
.catchErrorJustReturn(nil)
}.debug("property").shareReplayLatestWhileConnected()
let dashboard = property.flatMap { _ in self.interface.dashboard() }
dashboard.map { $0.display(property: property) }
.subscribe()
.disposed(by: bag)
}
let interface: Interface
let server: Server
let bag = DisposeBag()
}
do {
let interface = Interface()
let server = Server()
let coordinator = Coordinator(interface: interface, server: server)
coordinator.start()
assert(server._user != nil)
let simpleProperty = Property(id: "bar", name: "tampa")
let user = User(id: "foo", properties: [simpleProperty])
server._user?.onNext(user)
server._user?.onCompleted()
server._user = nil
assert(interface._login == nil)
assert(server._property != nil)
let property = Property(id: "bar", name: "tampa")
server._property!.onNext(property)
server._property!.onCompleted()
server._property = nil
assert(interface._dashboard != nil)
let dashboard = DashboardScreen()
interface._dashboard?.onNext(dashboard)
interface._dashboard?.onCompleted()
assert(dashboard._property != nil)
print("test passed")
}
这里是代码的输出作为其高于:
2017-06-01 22:22:42.534: property -> subscribed
2017-06-01 22:22:42.552: property -> Event next(Optional(__lldb_expr_134.Property(id: "bar", name: "tampa")))
2017-06-01 22:22:42.557: property -> Event completed
2017-06-01 22:22:42.557: property -> isDisposed
2017-06-01 22:22:42.559: property -> subscribed
assertion failed: file MyPlayground.playground, line 159
为什么property
被订阅后已经设置?
这里是输出,如果你删除\\
:
2017-06-01 22:23:51.540: property -> subscribed
2017-06-01 22:23:51.553: property -> Event next(Optional(__lldb_expr_136.Property(id: "bar", name: "tampa")))
test passed
我本来建议保持dashboard
绕了DisposeBag这样,当start()
完成时,参考不走太早。 OP已经更新了代码,因此这里有一个更新的答案。
当你添加更多的调试信息:
let dashboard = property.debug("prop in")
.flatMap { _ in self.interface.dashboard().debug("dash in") }
.debug("dash out")
日志将揭示该财产提前完成,之后即右内侧序列已订阅(“破折号 - >订阅” ):
2017-06-03 08:33:27.442: property -> Event next(Optional(Property(id: "bar", name: "tampa")))
2017-06-03 08:33:27.442: prop in -> Event next(Optional(Property(id: "bar", name: "tampa")))
2017-06-03 08:33:27.449: dash in -> subscribed
2017-06-03 08:33:27.452: property -> Event completed
2017-06-03 08:33:27.452: property -> isDisposed
2017-06-03 08:33:27.452: prop in -> Event completed
2017-06-03 08:33:27.452: prop in -> isDisposed
2017-06-03 08:33:27.456: dash in -> Event next(DashboardScreen)
2017-06-03 08:33:27.456: dash out -> Event next(DashboardScreen)
如果.concat(.never())
,完成事件不会触发,并且不会干扰过程。
的问题是你的测试代码编写势在必行。你start()
的过程,然后发布更改。但是如果你把各种onNext
事件异步地放到主队列上,整个事情就会更快崩溃。您的协调员的设计读取类似于声明性代码,但实际上它们的使用方式类似于一个很有说服力的顺序代码路径。
补救措施是要求及时性。PublishSubjects
没有历史记录;如果您使用的是BehaviorSubjects
重播他们的最新值,而不是,你可以设置所有的改变调用start()
之前,它会工作。我假设您使用PublishSubject
s,因为您首先打电话start()
打开管道,并希望通过它一个接一个地推送更改。问题是,你的管道是以不等待你推动所有事情的方式制造的。可以这么说,输入阀独立关闭。
呀,这个比喻是不是在人类历史:)
所以选择最好的真的是:
- 让所有的协调工作的一大
Observable.combineLatest
使整个改造序列ISN直到每个序列都有发言权, - 使用缓冲/重放主题并提前设置它们,
- 用一个从未完成的碱基序列替换
.just
(其完成)保持管道畅通;你可以将其设置为Observable<Observable<Credentials>>
,其中外部序列保持活动状态,内部序列使用Observable.just
- 尽管我怀疑你的生产代码将依赖于这个小细节。
添加DisposeBag没有帮助,断言仍然触发。对于我不了解的完成事件有一些特殊之处,或者RxSwift中存在错误。 –
既然我们没有你的行号,你至少可以给代码添加一个关于哪个断言失败的注释(159行)? – ctietze
对不起,这是最后一次失败。 –