组合中的 RxSwift `ActivityIndi​​cator` 功能

RxSwift `ActivityIndicator` Functionality in Combine

我已经使用 RxSwift 几年了,我开始探索 Combine with SwiftUI 并且在尝试从 Combine 中复制 RxSwift 的一些功能时遇到了一些麻烦。

在 RxSwift GitHub 上有一个名为 ActivityIndicator.swift.

的文件中的示例

基本用法如下:

class Foo {
  let activityIndicator = ActivityIndicator()

  lazy var activity = activityIndicator.asDriver()

  var disposeBag = DisposeBag()

  func doSomething() {
    Observable
      .just("this is something")
      .trackActivity(activityIndicator)
      .subscribe()
      .disposed(by: disposeBag)
  }
}

这样做的目的是让您可以离开 activity 驱动程序,并且它会在每次订阅或订阅完成时发出布尔值。

然后您可以使用 RxCocoa 直接驱动类似 UIActivityIndi​​catorView 的 isAnimating 属性 的东西。

我一直在尝试弄清楚如何在 Combine 中创建与此类似的东西,但没有任何运气。

假设我有一个如下所示的 viewModel:

class ViewModel: ObservableObject {
  @Published var isActive = false

  func doSomething() -> AnyPublisher<Void, Never> {
    Just(())
      .delay(for: 2.0, scheduler: RunLoop.main)
      .eraseToAnyPublisher()
  }
}

我想做的是为 Publisher 创建一个运算符,其功能类似于 Rx 运算符的工作方式,我可以通过链转发来自订阅的事件,但更改 isActive 每次值 subscribes/completes/cancels.

在 SwiftUI 视图中,我将启动 doSomething 函数并沉入其中,同时还能够使用已发布的 isActive 属性 到 show/hide ProgressView

与此类似的内容:

struct SomeView: View {
  let viewModel = ViewModel()

  var body: some View {
    var cancelBag = Set<AnyCancellable>()

    VStack {
      Text("This is text")

      if viewModel.isActive {
        ProgressView()
      }
    }
    .onAppear(perform: {
      viewModel
        .doSomething()
        .sink()
        .store(in: &cancelBag)
    })
  }
}

有没有我完全想念的像这样工作的东西?

如果没有,我该如何着手在 Combine 中复制 RxSwift 功能?

提前感谢您的帮助。

嗯... ActivityIndicator class 的关键是 Observable.using(_:observableFactory:) 运算符。不幸的是,我认为 Combine 中没有等效的运算符。

using 运算符在订阅 Observable 时创建资源,然后在 Observable 发送停止事件(完成或错误)时处置资源。这确保了资源的生命周期。在这种特殊情况下,资源只是在创建时增加 Int 值并在处置时减少它。

我想你可以用这样的东西来模仿这种行为:

extension Publisher {
    func trackActivity(_ activityIndicator: CombineActivityIndicator) -> some Publisher {
        return activityIndicator.trackActivity(of: self)
    }
}

final class CombineActivityIndicator {
    var counter = CurrentValueSubject<Int, Never>(0)
    var cancelables = Set<AnyCancellable>()

    func trackActivity<Source: Publisher>(of source: Source) -> some Publisher {
        let sharedSource = source.share()
        counter.value += 1
        sharedSource
            .sink(
                receiveCompletion: { [unowned self] _ in
                    self.counter.value -= 1
                },
                receiveValue: { _ in }
            )
            .store(in: &cancelables)
        return sharedSource
    }

    var asPublisher: AnyPublisher<Bool, Never> {
        counter
            .map { [=10=] > 0 }
            .eraseToAnyPublisher()
    }
}

但是,上面的 class 会使 Publisher 升温,您可能会因此错过发出的值。使用风险自负,除非你绝望,否则我不推荐以上内容。

也许有人已经为 Publisher 写了一个 using 运算符并且愿意分享。

看起来有人创建了 Combine 版本。我不知道它是否与@Daniel T. 讨论的问题相同,但看起来很有希望。 https://github.com/duyquang91/ActivityIndicator