MVVM с RxSwift - PullRequest
       13

MVVM с RxSwift

0 голосов
/ 29 апреля 2018

Я пытаюсь понять mvvm + RxSwift, но у меня есть несколько вопросов.

В настоящее время я использую этот подход, который я не уверен, является ли он правильным или может быть лучше. Как мне сделать, чтобы группировать методы, я имею в виду, может быть что-то вроде doFirst (loading = true) .doNext (getData) .doLast (loading = false) .catch (apiError), а затем подписаться на это событие? Это возможно?

ViewController:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel = UsersViewModel(apiService: apiService)
        configureBindings()
    }

    func configureBindings() {

        tableView.delegate = nil
        tableView.dataSource = nil

        viewModel.isLoading.bind(to: loadingView.rx.isAnimating)
            .disposed(by: disposeBag)

        viewModel.models
            .bind(to: tableView.rx.items(cellIdentifier: "userCell", cellType: UserCell.self)) {(_, _, cell) in
                print("Binding the cell items")
            }.disposed(by: disposeBag)

        tableView.rx.modelSelected(User.self).subscribe(onNext: { value in
            print(value)
        }).disposed(by: disposeBag)

        viewModel.error.filterNil().subscribe(onNext: { (err) in
            self.tableView.backgroundView = EmptyView(title: "No Users", description: "No users found")
            print("Showing empty view...")
            print(err)
        }).disposed(by: disposeBag)
    }
}

Тогда в моем UsersViewModel:

class UsersViewModel {

    var models: Observable<[User]> {
        return modelsVariable.asObservable()
    }

    var isLoading: Observable<Bool> {
        return isLoadingVariable.asObservable()
    }

    var error: Observable<ApiError?> {
        return errorVariable.asObservable()
    }

    private var modelsVariable = BehaviorRelay<[User]>(value: [])
    private var isLoadingVariable = BehaviorRelay<Bool>(value: false)
    private var errorVariable = BehaviorRelay<ApiError?>(value: nil)

    // MARK: - Data Manager
    var apiService: API

    required init(apiService: API) {
        self.apiService = apiService

        isLoadingVariable.accept(true)

        apiService.GET(EndPoints.USER_LIST, type: Several<User>.self)
            .subscribe(onNext: { (model) in
                self.isLoadingVariable.accept(false)
                self.modelsVariable.accept(model.items)
            }, onError: { (err) in
                self.isLoadingVariable.accept(false)
                self.errorVariable.accept(err as? ApiError)
            })
    }
}

Моя функция 'GET' просто возвращает Observable<Several<User>>.

Несколько:

struct Several {
    var items: [User]
}

Есть ли какие-либо улучшения, которые я могу сделать?

1 Ответ

0 голосов
/ 01 мая 2018

Немного трудно понять, о чем вы спрашиваете, но если вы обеспокоены императивным характером вашего init метода и хотите заключить вызов API в непрерывную наблюдаемую последовательность, которая может повторяться, вы может сделать что-то вроде этого:

class UsersViewModel {

    //...

    var fetchUsersObserver: AnyObserver<Void> {
        return fetchUsersSubject.asObserver()
    }

    //...

    private let fetchUsersSubject = PublishSubject<Void>()        
    private let disposeBag = DisposeBag()

    //...

    required init(apiService: API) {
        self.apiService = apiService
        bindFetchUsers()
    }

    private func bindFetchUsers() {
        fetchUsersSubject
            .asObservable()
            .do(onNext: { [weak self] _ in self?.isLoadingVariable.accept(true) })
            .flatMap(self.fetchUsers)
            .do(onNext: { [weak self] _ in self?.isLoadingVariable.accept(false) })
            .bind(to: modelsVariable)
            .disposed(by: disposeBag)
    }

    private func fetchUsers() -> Observable<[User]> {
        return apiService
            .GET(EndPoints.USER_LIST, type: Several<User>.self)
            .map { $0.items }
            .catchError { [weak self] error in
                self?.errorVariable.accept(error as? ApiError)
                return .just([])
            }
    }
}

Затем вам нужно только привязать элемент управления к этому AnyObserver или отправить ему событие вручную:

func configureBindings() {
    // from a control, such as UIButton
    someButton
        .rx
        .tap
        .bind(to: viewModel.fetchUsersObserver)
        .disposed(by: disposeBag)

    // manually
    viewModel.fetchUsersObserver.onNext(())
}

Сноска 1: Обычно я предпочитаю делать мои представления моделями struct s, чтобы мне не приходилось беспокоиться обо всех [weak self] утверждениях.

Сноска 2: Обратите внимание, как функция fetchUsers() перехватывает любые выданные ошибки и не позволяет ошибке распространяться на внешнюю наблюдаемую последовательность. Это важно, потому что, если этот внешний Observable испускает событие error, он никогда не сможет испустить другое событие next.

...