Rxswift:避免嵌套订阅

Rxswift: Avoid nested subscirption

我有一个要订阅的 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 here and a similar question here 但无法理解。请稍微解释一下您的回答,以便我可以学习解决类似的问题。

您走在正确的轨道上,flatMap 就是您想要的。可能让您感到困惑的是您想要订阅很多 Observable(在您的 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
}

注意上面是一个自由函数。不要在你的 class 中使用它;它不需要自我。

重构后,您的原始代码如下所示:

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()...所以让我们映射数组并合并可观察...

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: [=12=]) })
        .subscribe(onNext: { [viewModel = self!.viewModel] _ in
            doSomething()
        })
        .disposed(by: self!.disposeBag)
})
.disposed(by: disposeBag)

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

从这里...我们看到 Observable.merge(items.map(addViewToStack(from:))) 需要一个项目数组和 returns 一个单独的 Observable,这正是 flatMap 的签名所需要的...

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

现在我们没有了inner-subscribe,我们也不再需要捕获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([=14=].map { addViewToStack(from: [=14=]) }) }
            .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
}