目前,我正在使用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)
}
答案 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
函数应该是纯函数,没有副作用。在封包内部调用网络请求是不合适的。