2017-06-02 57 views
1

我知道有很多的代码在这里的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 
+0

既然我们没有你的行号,你至少可以给代码添加一个关于哪个断言失败的注释(159行)? – ctietze

+0

对不起,这是最后一次失败。 –

回答

1

我本来建议保持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()打开管道,并希望通过它一个接一个地推送更改。问题是,你的管道是以不等待你推动所有事情的方式制造的。可以这么说,输入阀独立关闭。

呀,这个比喻是不是在人类历史:)

所以选择最好的真的是:

  1. 让所有的协调工作的一大Observable.combineLatest使整个改造序列ISN直到每个序列都有发言权,
  2. 使用缓冲/重放主题并提前设置它们,
  3. 用一个从未完成的碱基序列替换.just(其完成)保持管道畅通;你可以将其设置为Observable<Observable<Credentials>>,其中外部序列保持活动状态,内部序列使用Observable.just - 尽管我怀疑你的生产代码将依赖于这个小细节。
+0

添加DisposeBag没有帮助,断言仍然触发。对于我不了解的完成事件有一些特殊之处,或者RxSwift中存在错误。 –