首页 > 解决方案 > RxSwift 如何拆分进度和结果 observables?

问题描述

我需要根据String输入进行长时间的异步计算并返回一个大Data实例。我使用Singletrait 来实现这一点:

func calculateData(from: String) -> Single<Data>

这个例子很简单并且有效。但我还需要跟踪进度——一个介于 0 和 1 之间的数字。我正在做这样的事情:

func calculateData(from: String) -> Observable<(Float, Data?)>

我得到以下序列:

next: (0, nil)
next: (0.25, nil)
next: (0.5, nil)
next: (0.75, nil)
next: (1, result data)
complete

我检查进度和数据以了解我是否有结果,它有效,但我觉得这里有一些强烈的气味。我想分开流:Observable有进展和Single结果。我知道我可以返回一个包含两个可观察对象的元组或结构,但我也不喜欢这样。

我怎样才能做到这一点?是否可以?

标签: reactive-programmingrx-swift

解决方案


你有什么很好,虽然我会命名元组中的元素


func calculateData(from: String) -> Observable<(percent: Float, data: Data?)>

let result = calculateData(from: myString)
    .share()

result
    .map { $0.percent }
    .subscribe(onNext: { print("percent complete:", $0) }
    .disposed(by: disposeBag)

result
    .compactMap { $0.data }
    .subscribe(onNext: { print("completed data:", $0) }
    .disposed(by: disposeBag)

另一种选择是使用一个返回完成百分比或数据的枚举:

enum Progress {
    case incomplete(Float)
    case complete(Data)
}

func calculateData(from: String) -> Observable<Progress>

但是,这样做会使将 Observable 分成两个流变得更加困难。为此,您必须像这样扩展 Progress:

extension Progress {
    var percent: Float {
        switch self {
        case .incomplete(let percent):
            return percent
        case .complete:
            return 1
        }
    }

    var data: Data? {
        switch self {
        case .incomplete:
            return nil
        case .complete(let data):
            return data
        }
    }
}

如您所见,执行上述操作实质上会将枚举转换为您已经在使用的元组。不过,这样做的好处是您获得了编译时保证,即如果 Data 发出,则进度将为 1。

如果您想要两全其美,请使用结构:

struct Progress {
    let percent: Float
    let data: Data?

    init(percent: Float) {
        guard 0 <= percent && percent < 1 else { fatalError() }
        self.percent = percent
        self.data = nil
    }

    init(data: Data) {
        self.percent = 1
        self.data = data
    }
}

func calculateData(from: String) -> Observable<Progress>

以上提供了枚举的编译时间保证以及使用元组获得的拆分的便利性。它还提供了运行时保证进度将为 0...1,如果为 1,则数据将存在。


推荐阅读