在 RxSwift-MVVM 架构中,触发 UI 元素(如弹出窗口和指示器)的最佳方式是什么?

In the RxSwift-MVVM architecture what is the best way to trigger UI elements like pop-ups and indicators?

问题是 - 哪里最好:

  1. 调用错误处理弹出窗口
  2. Show/Hide 加载指标

我的应用程序如下所示:

ViewController订阅模型变化时UI更新的触发器:

 var viewModel: ViewModel = ViewModel()

...

 viewModel.source.asObservable().subscribe(onNext: { (_ ) in
      self.tableView.reloadData()
     })
    .disposed(by: bag)

ViewModel

     var source = Variable<[Student]>([])

并且在初始化时获取源输出

     api.fetchSourceOutput(id: id)
         .do(onError: { (error) in
                //show error here???
         })
         .catchErrorJustReturn([])
         .bind(to: source)
         .disposed(by: bag)

我不能只将 ViewController 的引用传递给 ViewModel,这会破坏它独立于 UI 的想法。那么我应该如何在视图控制器的视图中调用错误弹出窗口?获得顶视图控制器也不是一个好的选择,因为我可能需要特定的视图来显示我的弹出窗口。 加载指示器可以在 viewModel 内部调用 onNext 并隐藏 onCompleted 时显示。但是我再次没有引用我的加载指示器引用所在的视图控制器。

想法?

我会在您的 viewModel 中进行此更改:

// MARK: - Properties
let identifier = Variable(0)

lazy var source: Observable<[Student]> = identifier.asObservable()
    .skip(1)
    .flatMapLatest { id in 
        return api.fetchSourceOutput(id: id)
    }
    .observeOn(MainScheduler.instance)
    .share(replay: 1)

...

// MARK: - Initialization
init(id: Int) {
    identifier.value = id
    ...
}

然后,在您的 ViewController:

viewModel.source
    .subscribe(onNext: { _ in
        self.tableView.reloadData()
     }, onError: { error in 
        // Manage errors
     })
    .disposed(by: bag)

Call error handling popups

假设你有一些信号开始 api fetch

let someSignalWithIdToStartApiFetch = Observable.just(1)

此外,让我们想象一下,当您在出现错误时显示一些 "retry request" 弹出窗口,并且当用户单击 "retry" 按钮时,您将其绑定到某个观察者。然后将观察者转换为Observable。所以你有一些第二个信号:

let someSignalWhenUserAsksToRetryRequestAfterError = Observable.just(())

当您需要重试请求时,您可以这样从 someSignalWithIdToStartApiFetch 获取最后一个 ID:

let someSignalWithIdToRetryApiFetch = someSignalWhenUserAsksToRetryRequestAfterError
    .withLatestFrom(someSignalWithIdToStartApiFetch)
    .share(replay: 1, scope: .whileConnected)

然后你结合两个信号并发出请求:

let apiFetch = Observable
    .of(someSignalWithIdToRetryApiFetch, someSignalWithIdToStartApiFetch)
    .merge()
    .flatMap({ id -> Observable<Response> in
        return api
            .fetchSourceOutput(id: id)
            .map({ Response.success([=13=]) })
            .catchError({ Observable.just(Response.error([=13=])) })
    })
    .share(replay: 1, scope: .whileConnected)

如您所见,错误已被捕获并转换为某种结果。例如:

enum Response {
    case error(Error)
    case success([Student])
    var error: Error? {
        switch self {
        case .error(let error):         return error
        default:                        return nil
        }
    }
    var students: [Student]? {
        switch self {
        case .success(let students):    return students
        default:                        return nil
        }
    }
}

然后你像往常一样工作成功结果:

apiFetch
    .map({ [=15=].students })
    .filterNil()
    .bind(to: source)
    .disposed(by: bag)

但错误案例应该绑定到触发弹出窗口的某个观察者:

apiFetch
    .map({ [=16=].error })
    .filterNil()
    .bind(to: observerWhichShowsPopUpWithRetryButton)
    .disposed(by: bag)

因此,当显示弹出窗口并且用户单击 "retry" - someSignalWhenUserAsksToRetryRequestAfterError 将触发并重试请求

Show/Hide loading indicator

我使用类似 this 的东西。它是一种特殊的结构,可以捕获可观察对象的 activity 。如何使用它?

let indicator = ActivityIndicator()

以及问题第一部分的一些代码。

let apiFetch = Observable
    .of(someSignalWithIdToRetryApiFetch, someSignalWithIdToStartApiFetch)
    .merge()
    .flatMap({ id -> Observable<[Student]> in
        return indicator
            .trackActivity(api.fetchSourceOutput(id: id))
    })
    .map({ Response.success([=18=]) })
    .catchError({ Observable.just(Response.error([=18=])) })
    .share(replay: 1, scope: .whileConnected)

所以,activity of api fetch 被跟踪了。现在您应该 show/hide 您的 activity 视图。

let observableActivity = indicator.asObservable() // Observable<Bool>
let observableShowLoading = observableActivity.filter({ [=19=] == true })
let observableHideLoading = observableActivity.filter({ [=19=] == false })

observableShowLoadingobservableHideLoading 绑定到 hide/show 函数。即使您有多个可能同时执行的请求 - 将它们全部绑定到一个 ActivityIndicator.

希望对您有所帮助。编码愉快(^