首页 > 解决方案 > Swift Combine:未来会做异步工作吗?

问题描述

我有一个名为的对象ProcessorStack,其中包含零个或多个子Processor项。ProcessorStack和单独的对象Processor每个只有一个方法:

process(input: Value) -> Future<Value, Never>

我希望返回值是 aFuture而不是AnyPublisher清楚地表明调用者应该只期望发出一个结果。其他对象只能访问ProcessorStack,而不是其Processor子对象。这就是我想要发生的事情:

  1. 一个对象调用ProcessorStack
    stack.process(value: someValue).sink { result in
        // Do something with the result
    }.store(in: &subscriptions)
    
  2. 使用 reduce 操作将其ProcessorStack所有子Processor对象链接在一起,并通过以下方式返回最终结果Future
    func process(value: Value) -> Future<Value, Never> {
        guard !childProcessors.isEmpty else {
            return Future { $0(.success(value)) }
        }
        let just = Just(value).eraseToAnyPublisher()
        childProcessors.reduce(just) { (publisher, processor) -> AnyPublisher<Value, Never> in
            publisher.flatMap { processor.process(value: $0).eraseToAnyPublisher() }
        }
        // Here's where I'm lost.
    }
    

我一生都无法弄清楚如何执行异步 reduce 链,然后将结果作为Future. 如果我将整个 reduce 操作包装在一个Future初始化程序中,我将保留一个AnyPublisher<Value, Never>我必须以某种方式执行的操作,然后将其结果传递给Future's 的完成闭包。我不能sinkFuture's 闭包中使用它,因为我必须坚持从那里返回的可取消,否则整个过程会立即停止。我不能将结果平面映射到 Future,因为它的类型是FlatMap<AnyPublisher<Value, Never>, Future<Value, Never>>. 如果我只制作外部返回类型AnyPublisher<Value, Never>,我可以完成所有这些,但我真的希望拥有Future订阅者的语义。

标签: swiftcombine

解决方案


您尝试做的事情的前提是错误的。Future,尽管它最多只能返回一个结果,但在语义上并不代表这一点。它只是一种特定类型的发布者。

您应该AnyPublisher在函数边界返回 a ,而不是试图避免它,这将使您的代码对更改更加健壮(例如,如果您需要将 a 包装Future在 a 中Deferred怎么办? - 一种常见做法)

process(value: Value) -> AnyPublisher<Value, Never> {
   ...
}

如果订阅者只能处理一个结果,他们可以简单地确保first()

process(value)
    .first()
    .sink {...}
    .store(in: &storage)

但是如果你坚持,你可以使用.sinkinsideFuture的闭包,如果闭包捕获了对的引用AnyCancellable并在完成时释放它:

process(value: Value) -> Future<Value, Never> {
    guard !childProcessors.isEmpty else {
        return Future { $0(.success(value)) }
    }
    let just = Just(value).eraseToAnyPublisher()
    let combined = childProcessors.reduce(just) { (publisher, processor) -> AnyPublisher<Value, Never> in
        publisher.flatMap { processor.process(value: $0).eraseToAnyPublisher() }
    }

    var c: AnyCancellable? = nil
    return Future { promise in
        c = combined.sink(receiveCompletion: { 
            withExtendedLifetime(c){}; c = nil 
        }) {
            promise(.success($0))
        }
    }
}

推荐阅读