Я знаю, что государство - враг Реактивного программирования, но я имею дело с ним в процессе изучения RxSwift.
Мое приложение очень простое: первый экран представляет собой список и поиск книг, а второй - подробное описание книги, в которой вы можете добавить / удалить книгу на полку и отметить ее как чтение / непрочитанных .
Чтобы показать детали книги, я создаю BookViewModel
, передавая BooksService
для выполнения сетевых операций и текущий Book
для отображения.
Проблема в том, что мне нужно отслеживать изменения в книге, чтобы изменить пользовательский интерфейс: например, после удаления книги кнопка, которая ранее говорила «Удалить», теперь должна сказать «Добавить».
Я добиваюсь этого поведения, используя Variable<Book>
, выставляемый наблюдателям как Driver<Book>
, но я много путаюсь с ним, когда операция сети возвращается, и мне нужно обновить значение Variable<Book>
, чтобы вызвать обновление пользовательского интерфейса.
Это инициализатор модели представления:
init(book: Book, booksService: BooksService) {
self._book = Variable(book)
self.booksService = booksService
}
Это наблюдаемое мной разоблачение
var book: Driver<Book> {
return _book.asDriver()
}
А вот моя функция добавить / удалить книгу:
func set(toggleInShelfTrigger: Observable<Void>) {
toggleInShelfTrigger // An observable from a UIBarButtonItem tap
.map({ self._book.value }) // I have to map the variable's value to the actual book
.flatMap({ [booksService] book -> Observable<Book> in
return (book.isInShelf ?
booksService.delete(book: book) :
booksService.add(book: book))
}) // Here I have to know if the books is in the shelf or not in order to perform one operation or another.
.subscribe(onNext: { self._book.value = $0 }) // I have to update the variable's value in order to trigger the UI update
.disposed(by: disposeBag)
}
Я очень недоволен этим кодом и всей моделью представления. Это работает, но это неуклюже, и по существу неправильно, потому что, если работа сети терпит неудачу, подписка будет уничтожена, и моя кнопка станет не отвечающей.
Если я избавлюсь от Variable<Book>
и верну Driver<Book>
из метода set(toggleInShelfTrigger: Observable<Void>)
, у меня не будет этого беспорядка, но я не смогу узнать, нужно ли мне добавлять или удалять книгу.
Итак, как же в реальном мире способ отслеживать состояние объекта в таком приложении? Как я могу достичь этого, используя только операторы Rx?
EDIT
Мне удалось очистить этот дерьмовый код, но я все еще пытаюсь достичь состояния без Variable
и использования оператора scan
.
Это мой новый BookViewModel
инициализатор:
init(book: Book, booksService: BooksService) {
self.bookVariable = Variable(book)
let addResult = addBook
.mapBookFrom(bookVariable)
.flatMapLatest({ booksService.add(book: $0) })
.updateBookVariable(bookVariable)
let removeResult = ... // Similar to addResult, changing service call
let markReadResult = ... // Similar to addResult, changing service call
let markUnreadResult = ... // Similar to addResult, changing service call
self.book = Observable.of(addResult, removeResult, markReadResult, markUnreadResult).merge()
.startWith(.success(book))
}
Я сделал несколько пользовательских операторов, чтобы помочь мне управлять Variable<Book>
, один, чтобы получить настоящий Book
:
private extension ObservableType where E == Void {
func mapBookFrom(_ variable: Variable<Book>) -> Observable<Book> {
return map({ _ in return variable.value })
}
}
И еще, чтобы обновить Variable
после возврата службы:
private extension ObservableType where E == BookResult<Book> {
func updateBookVariable(_ variable: Variable<Book>) -> Observable<BookResult<Book>> {
return self.do(onNext: { result in
if case let .success(book) = result {
variable.value = book
}
})
}
}
Теперь у меня очень чистый вид модели, но не "идеальный".