Объедините сканирование с сетевым запросом для обновления модели в RxSwift - PullRequest
1 голос
/ 27 июня 2019

В настоящее время у меня Observable, созданный с использованием scan для обновления базовой модели с использованием PublishSubject, например:

class ViewModel {

    private enum Action {
        case updateName(String)
    }

    private let product: Observable<Product>
    private let actions = PublishSubject<Action>()

    init(initialProduct: Product) {
        product = actions
            .scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
                var newProduct = oldProduct

                switch action {
                case .updateName(let name):
                    newProduct.name = name
                }

                return newProduct
            })
            .startWith(initialProduct)
            .share()
    }

    func updateProductName(_ name: String) {
        actions.onNext(.updateName(name))
    }

    private func getProductDetail() {
        /// This will call a network request
    }
}

Каждое "локальное" действие, такое как обновление названия продукта, цены ... выполняется с помощью метода, подобного updateProductName(_ name: String) выше. Но что, если я хочу получить сетевой запрос, который также обновляет продукт и может вызываться каждый раз, когда я хочу, например, после нажатия кнопки или после вызова updateProductName?

// ОБНОВЛЕНИЕ: После прочтения комментария iWheelBuy и ответа Даниэля я в итоге использовал еще 2 действия

class ViewModel {

    private enum Action {
        case getDetail
        case updateProduct(Product)
    }

    ///....

    init(initialProduct: Product) {
        product = actions
            .scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
                var newProduct = oldProduct

                switch action {
                case .updateName(let name):
                    newProduct.name = name

                case .getDetail:
                    self.getProductDetail()

                case .updateProduct(let p):
                    return p
                }

                return newProduct
            })
            .startWith(initialProduct)
            .share()
    }

    func getProductDetail() {
        actions.onNext(.getDetail)
    }

    private func getProductDetail(id: Int) {
        ProductService.getProductDetail(id) { product in
            self.actions.onNext(.updateProduct(product))
        }
    }
}

Но я чувствую, что я запускаю побочный эффект (вызов сетевого запроса) внутри scan, без обновления модели, это что-то не так?

Также, как я могу использовать сетевой запрос "rx"?

    // What if I want to use this method instead of the one above,
    // without subscribe inside viewmodel?
    private func rxGetProductDetail(id: Int) -> Observable<Product> {
        return ProductService.rxGetProductDetail(id: Int)
    }

1 Ответ

1 голос
/ 28 июня 2019

Я не уверен, почему @iWheelBuy не дал реального ответа, потому что их комментарий - правильный ответ.Учитывая гибридный подход к Rx в вашем вопросе, я ожидаю, что что-то вроде приведенного ниже будет соответствовать вашему стилю:

class ViewModel {

    private enum Action {
        case updateName(String)
        case updateProduct(Product)
    }

    private let product: Observable<Product>
    private let actions = PublishSubject<Action>()
    private var disposable: Disposable?

    init(initialProduct: Product) {
        product = actions
            .scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
                var newProduct = oldProduct

                switch action {
                case .updateName(let name):
                    newProduct.name = name
                case .updateProduct(let product):
                    newProduct = product
                }

                return newProduct
            })
            .startWith(initialProduct)
            .share()

            // without a subscribe, none of this matters. I assume you just didn't show all your code.
    }

    deinit {
        disposable?.dispose()
    }

    func updateProductName(_ name: String) {
        actions.onNext(.updateName(name))
    }

    private func getProductDetail() {
        let request = URLRequest(url: URL(string: "https://foo.com")!)
        disposable?.dispose()
        disposable = URLSession.shared.rx.data(request: request)
            .map { try JSONDecoder().decode(Product.self, from: $0) }
            .map { Action.updateProduct($0) }
            .subscribe(
                onNext: { [actions] in actions.onNext($0) },
                onError: { error in /* handle error */ }
        )
    }
}

Приведенный выше стиль все еще довольно обязателен, но если вы не хотите, чтобы использование Rx протекаловне видовой модели все в порядке.

Если вы хотите увидеть установку "full Rx", вы можете найти мой пример репо интересным: https://github.com/danielt1263/RxEarthquake

ОБНОВЛЕНИЕ

Но я чувствую, что я запускаю побочный эффект (вызов сетевого запроса) при сканировании, не обновляя модель, это что-то не так?

Функция scan должна быть чистой, без сторонпоследствия.Вызов сетевого запроса внутри его закрытия неуместен.

...