首页 > 解决方案 > 全部完成后 ReactiveSwift 管道计数失败

问题描述

我在ReactiveSwift中有一个用于上传的管道。我想确保即使其中一个上传失败,其余的也不会被中断。

在所有这些都成功或失败后,我应该从performUploads方法中返回成功,或者如果有任何失败,我应该返回错误,所以下一步,下载部分不会开始。即使有错误,所有上传都应该有机会上传,它们不应该被停止。

有没有办法确定上传完成后是否有任何错误?请参阅此处的方法:

let pendingUploadItemsArray: [Items] = ...
func performUploads() -> SignalProducer<(), MyError> {
    return upload(pendingUploadItemsArray)
        .then(doAnything())
}

private func upload(_ items: [Items]) -> SignalProducer<Signal<(), MyError>.Event, Never> {
    let producers = items
        .filter { item in
            return item.readyForUpload
        }
        .map { self.upload($0).materialize() }
    
    return SignalProducer.merge(producers)
}

private func upload(_ item: Item) -> SignalProducer<(), MyError> {
    return internalUploader.upload(item)
        .on(failed: failedToUpload(item),
            value: successfullyUploaded(item))
        .ignoreValues()
}

internalUploader上传方法是:

func upload(_ item: Item) -> SignalProducer<Any, MyError>

然后在另一个类中,您将调用此上传器:

let sync = self.uploader.performUploads()
        .then(startDownloads())

只有在startDownloads所有上传都成功完成时才应该运行。感谢您的任何见解。

这可能是应该以完全不同的方式完成的事情。

标签: iosswiftreactive-swift

解决方案


我不知道您的代码到底在做什么successfullyUploadedfailedToUpload在做什么,但大概您正在跟踪成功和失败以提供某种实时进度 UI。这就是我将如何构建它:

struct UploadResult {
    let item: Item
    let error: Error? // nil if the upload succeeded

    var succeeded: Bool { error == nil }
    var failed: Bool { !succeeded }
}

...

static func upload(_ items: [Item]) -> SignalProducer<[UploadResult], Never> {
    SignalProducer(items)
        .filter(\.readyForUpload)
        .flatMap(.merge) { item in
            Self.internalUploader(item)
                .map { UploadResult(item: item, error: nil) }
                .flatMapError { error in
                    SignalProducer(value: UploadResult(item: item, error: error))
                }
        }
        .scan(into: [UploadResult]()) { ( results: inout [UploadResult], nextResult) in
            results.append(nextResult)
        }
}
  1. 我创建了一个UploadResult结构,它表示上传成功或失败的项目。
  2. upload函数中,我不是创建一个生产者数组然后合并它们,而是将项目数组转换为项目的信号生产者,SignalProducer(items)然后使用flatMap(.merge)将上传的内容合并为单个信号生产者。
  3. materialize我没有使用 ,而是将map成功上传的文件转换为.UploadResultflatMapErrorUploadResult
  4. 我用来scan在每次上传完成时累积结果。每次上传完成(成功或出错)时,scan将发送更新的上传结果数组,可用于更新 UI。

然后你可以像这样使用它:

Uploader.upload(someItems)
    .on(value: { resultsSoFar in
        // Update UI here
    })
    .take(last: 1)
    .attempt { results in
        if !results.allSatisfy(\.succeeded) {
            // At least one of the uploads failed, so send an error
            throw MyError()
        }
    }
    .then(startDownloads)
  1. 我使用on(value:)运算符根据当前结果更新 UI。每次下载成功或失败时,都会使用更新的结果调用此闭包。
  2. take(last: 1)用来过滤掉所有中间结果;它只会在所有上传完成后发送最终结果。
  3. attempt用来检查是否有任何上传失败,如果失败则抛出错误。这确保只有在所有上传成功后才会开始下载。

希望这可以处理您的用例,但是如果我错过了有关更广泛背景的某些内容,请在评论中告诉我!

编辑

如果您只关心一次处理一个结果而不是作为一个正在运行的数组,您可以摆脱scan然后替换take(last: 1)collect

static func upload(_ items: [Item]) -> SignalProducer<UploadResult, Never> {
    SignalProducer(items)
        .filter(\.readyForUpload)
        .flatMap(.merge) { item in
            Self.internalUploader(item)
                .map { UploadResult(item: item, error: nil) }
                .flatMapError { error in
                    SignalProducer(value: UploadResult(item: item, error: error))
                }
        }
}

...

Uploader.upload(someItems)
    .on(value: { latestResult in
        // Do something with the latest result
    })
    .collect()
    .attempt { results in
        if !results.allSatisfy(\.succeeded) {
            // At least one of the uploads failed, so send an error
            throw MyError()
        }
    }
    .then(startDownloads)

推荐阅读