将扫描与网络请求结合起来以更新RxSwift中的模型

时间:2019-06-27 13:08:29

标签: reactive-programming rx-swift

目前,我正在使用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的评论和Daniel的回答后,我最终又使用了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 个答案:

答案 0 :(得分:1)

我不确定@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泄漏出视图模型,那就可以了。

如果您想查看“完整Rx”设置,则可能会发现我的示例存储库很有趣:https://github.com/danielt1263/RxEarthquake

更新

  

但是我感觉到,在不更新模型的情况下,我在扫描过程中触发了副作用(呼叫网络请求),这有问题吗?

scan函数应该是纯函数,没有副作用。在封包内部调用网络请求是不合适的。