Rxswift:避免嵌套订阅



我有一个对象的Observable数组订阅,每个对象必须绑定一个UI组件和UI组件内,我必须添加点击手势的子视图,这需要另一个订阅。我是RxSwift的新手,读了一些类似的问题和文章,但要么我不理解,要么内容不太相关。

items.subscribe(onNext: { [weak self] items in
for item in items {
let view = aView()
view.label.text = item.name
view.image.rx.touchUpInside.subscribe(onNext: { [viewModel] _ in
doSomething()
}).disposed(by: disposeBag)
self?.stackView.addArrangedSubview(view)
}
}).disposed(by: disposeBag)

我在这里读过flatMap和一个类似的问题,但无法理解它。请解释一下你的答案,这样我可以学习处理类似的问题。

您在正确的轨道上,因为flatMap是您想要的。您可能会感到困惑的是,您想要订阅许多Observables(在您的for循环中),而不仅仅是一个。我们先来分析一下。

对于你的数组中的每个项目,你想创建一个视图,命名它,把它添加到你的stackView,然后听touchUpInside观察。因此,为此创建一个函数(这实际上是将for循环的主体转换为一个自由函数):

func addView(to stackView: UIStackView, for item: Item) -> Observable<Void> {
let view = aView()
view.label.text = item.name
stackView.addArrangedSubview(view)
return view.image.rx.touchUpInside
}

注意,上面是一个自由函数。不要让它成为类中的方法;它不需要self.

在重构之后,你的原始代码看起来像这样:
items.subscribe(onNext: { [weak self] items in
for item in items {
addView(to: self!.stackView, for: item)
.subscribe(onNext: { [viewModel = self!.viewModel] _ in
doSomething()
})
.disposed(by: self!.disposeBag)
}
})
.disposed(by: disposeBag)

接下来,我们看到您正在对数组中的每个项目执行相同的操作,并且它们的所有订阅都将导致调用相同的doSomething()…那么让我们来映射数组并合并observable…

func addViewToStack(from item: Item) -> Observable<Void> {
addView(to: stackView, for: item)
}
items.subscribe(onNext: { [weak self] items in
Observable.merge(items.map { addViewToStack(from: $0) })
.subscribe(onNext: { [viewModel = self!.viewModel] _ in
doSomething()
})
.disposed(by: self!.disposeBag)
})
.disposed(by: disposeBag)

注意,将addViewToStack(from:)函数嵌入到当前函数中;不要让它成为一种方法。这样它将捕获stackView而不捕获self。

从这里…我们看到Observable.merge(items.map(addViewToStack(from:)))需要一个项目数组并返回一个Observable,这正是flatMap的签名所要求的…

items
.flatMap { Observable.merge($0.map { addViewToStack(from: $0) }) }
.subscribe(onNext: { [viewModel] _ in
doSomething()
})
.disposed(by: disposeBag)

现在我们没有内部订阅了,我们也不再需要捕获self。


综合起来,结果看起来像这样:

class Example {
let stackView = UIStackView()
let viewModel = ViewModel()
let disposeBag = DisposeBag()
func example(items: Observable<[Item]>) {
func addViewToStack(from item: Item) -> Observable<Void> {
addView(to: stackView, for: item)
}
items
.flatMap { Observable.merge($0.map { addViewToStack(from: $0) }) }
.subscribe(onNext: { [viewModel] _ in
doSomething()
})
.disposed(by: disposeBag)
}
}
func addView(to stackView: UIStackView, for item: Item) -> Observable<Void> {
let view = aView()
view.label.text = item.name
stackView.addArrangedSubview(view)
return view.image.rx.touchUpInside
}

最新更新