如何在 AnyPublisher<Void, Never> 和 AnyPublisher<Never, Never> 之间进行转换?

Sen*_*ful 9 swift combine

我将异步请求包装在组合发布者中,以便可以轻松地跨不同管道使用它们。

消费者可能会按如下方式保留这些发布者:

struct Dependencies {
  var loadImageRequest: AnyPublisher<UIImage, Never>
  var saveToDatabaseRequest: AnyPublisher<Void, Never>
  var saveToUserDefaultsRequest: AnyPublisher<Never, Never>
}
Run Code Online (Sandbox Code Playgroud)

两种更常见的请求类型是:

  1. 即发即忘(立即完成):例如,将值保存到用户默认值,可以建模为即发即忘。到目前为止,这似乎AnyPublisher<Never, Never>是表达这种类型的好方法。这可以很容易地通过构建Empty<Never, Never>(completeImmediately: true)
  2. 触发、等待并忽略结果:一个示例是将值保存到数据库(并忽略结果),但仍希望等到保存完成后再继续管道。我一直在对AnyPublisher<Void, Never>这些请求类型进行建模。构建这些的一个简单方法是通过Future<Void, Never>() { promise in promise(.success(()))}.

两者都有一个共同的主题,那就是忽视结果。因此,当将它们交给消费者时,有时在这两种数据类型之间进行转换很有用:AnyPublisher<Never, Never>AnyPublisher<Void, Never>

可以通过三种方式在两者之间进行转换:

  1. Never -> Void,立即完成

    转换它的一种方法是使用一些强制转换:

    let neverPublisher: AnyPublisher<Never, Never> = ...
    let voidPublisher: AnyPublisher<Void, Never> = neverPublisher
      .map { _ in () }
      .append(Just(()))
      .eraseToAnyPublisher()
    
    Run Code Online (Sandbox Code Playgroud)
  2. Void -> Never,等待Void完成

    由于有一个内置运算符,因此转换很容易:

    let voidPublisher: AnyPublisher<Void, Never> = ...
    let neverPublisher: AnyPublisher<Never, Never> = voidPublisher
      .ignoreOutput()
      .eraseToAnyPublisher()
    
    Run Code Online (Sandbox Code Playgroud)
  3. Void -> Never,立即完成

    我不确定进行此转换的最佳方法。我提出的解决方案有两个主要缺点:使用handleEvents,并且需要cancellables在某处定义:

    let cancellables: Set<AnyCancellable> = []
    let voidPublisher: AnyPublisher<Void, Never> = ...
    let neverPublisher: AnyPublisher<Never, Never> = Empty<Never, Never>(completeImmediately: true)
      .handleEvents(receiveCompletion: { _ in voidPublisher.sink(receiveValue: { _ in }).store(in: &cancellables) })
      .eraseToAnyPublisher()
    
    Run Code Online (Sandbox Code Playgroud)

问题:

  1. 有没有更好的方法来进行转换#1(Never -> Void立即完成)而不需要同时调用mapand append?(例如类似于如何ignoreOutput用于解决第二次转换?)
  2. 有没有更好的方法来进行Void -> Never不需要的第三次转换(立即完成)cancellables

Sen*_*ful 7

我还没有找到更优雅的方法来完成#2。

\n

这是更好的#3(Void -> Never立即完成)解决方案:

\n
let voidPublisher: AnyPublisher<Void, Never> = ...\nlet neverPublisher: AnyPublisher<Never, Never> = Publishers.Merge(Just<Void>(()), saveToDatabase)\n  .first()\n  .ignoreOutput()\n  .eraseToAnyPublisher()\n
Run Code Online (Sandbox Code Playgroud)\n
\n

这是我用来确保一切按预期运行的计时游乐场:

\n
import Combine\nimport Foundation\n\nlet delaySec: TimeInterval = 0.1\nlet halfDelaySec: TimeInterval = delaySec / 2\nlet halfDelayMicroSeconds: useconds_t = useconds_t(halfDelaySec * 1_000_000)\nlet sleepBufferMicroSeconds: useconds_t = useconds_t(0.01 * 1_000_000)\n\nvar cancellables = [AnyCancellable]()\nvar output: [String] = []\n\nfunc performVoidAction(voidPublisher: AnyPublisher<Void, Never>) {\n  voidPublisher\n    .handleEvents(\n      receiveCompletion: { _ in output.append("performVoidAction - completion") },\n      receiveCancel: { output.append("performVoidAction - cancel") })\n    .sink(receiveValue: { output.append("performVoidAction - sink") })\n    .store(in: &cancellables)\n}\n\nfunc performNeverAction(neverPublisher: AnyPublisher<Never, Never>) {\n  neverPublisher\n    .handleEvents(\n      receiveCompletion: { _ in output.append("performNeverAction - completion") },\n      receiveCancel: { output.append("performNeverAction - cancel") })\n    .sink(receiveValue: { _ in output.append("performNeverAction - sink") })\n    .store(in: &cancellables)\n}\n\nfunc makeSaveToDatabasePublisher() -> AnyPublisher<Void, Never> {\n  Deferred { _saveToDatabase() }.eraseToAnyPublisher()\n}\n\nfunc makeSaveToUserDefaultsPublisher() -> AnyPublisher<Never, Never> {\n  Deferred { _saveToUserDefaults() }.eraseToAnyPublisher()\n}\n\n// --->(Void)|\n// AnyPublisher<Void, Never> wraps an API that does something and finishes upon completion.\nprivate func _saveToDatabase() -> AnyPublisher<Void, Never> {\n  return Future<Void, Never> { promise in\n    output.append("saving to database")\n    DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: DispatchTime.now() + delaySec) {\n      output.append("saved to database")\n      promise(.success(()))\n    }\n  }.eraseToAnyPublisher()\n}\n\n// |\n// AnyPublisher<Never, Never> wraps an API that does something and completes immediately, it does not wait for completion.\nprivate func _saveToUserDefaults() -> AnyPublisher<Never, Never> {\n  output.append("saved to user defaults")\n  return Empty<Never, Never>(completeImmediately: true)\n    .eraseToAnyPublisher()\n}\n\nfunc assert(_ value: Bool) -> String {\n  value ? "\xe2\x9c\x85" : "\xe2\x9d\x8c"\n}\n\n// tests\nassert(output.isEmpty)\n\nvar saveToDatabase = makeSaveToDatabasePublisher()\nassert(output.isEmpty, "It should not fire the action yet.")\n\n// verify database save, first time\nperformVoidAction(voidPublisher: saveToDatabase)\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")\nassert(output.isEmpty)\n\n// verify database save, second time\nperformVoidAction(voidPublisher: saveToDatabase)\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")\nassert(output.isEmpty)\n\nvar saveToUserDefaults = makeSaveToUserDefaultsPublisher()\nassert(output.isEmpty, "It should not fire the action yet.")\n\n// verify user defaults save, first time\nperformNeverAction(neverPublisher: saveToUserDefaults)\nassert(!output.isEmpty && output.removeFirst() == "saved to user defaults")\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(output.isEmpty) // \'perform never action\' should never be output\n\n// verify user defaults save, second time\nperformNeverAction(neverPublisher: saveToUserDefaults)\nassert(!output.isEmpty && output.removeFirst() == "saved to user defaults")\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(output.isEmpty) // \'perform never action\' should never be output\n\n// MARK: - Problem: AnyPublisher<Never, Never> -> AnyPublisher<Void, Never>\n\n// MARK: Solution 1\n// `|` \xe2\x9e\xa1\xef\xb8\x8f `(Void)|`\n\nperformVoidAction(\n  voidPublisher: saveToUserDefaults\n    .map { _ in () }\n    .append(Just(()))\n    .eraseToAnyPublisher())\nassert(output.removeFirst() == "saved to user defaults")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - sink")\nassert(!output.isEmpty && output.removeFirst() == "performVoidAction - completion")\nassert(output.isEmpty) // \'perform never action\' should never be output"\n\n\n// MARK: - Problem: AnyPublisher<Void, Never> -> AnyPublisher<Never, Never>\n\n// MARK: Solution 2 (Wait)\n// `--->(Void)|` \xe2\x9e\xa1\xef\xb8\x8f `--->|`\n\nperformNeverAction(\n  neverPublisher: saveToDatabase.ignoreOutput().eraseToAnyPublisher())\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(output.isEmpty)\n\n\n// MARK: Solution 3 (No wait)\n// `--->(Void)|` \xe2\x9e\xa1\xef\xb8\x8f `|`\n\nperformNeverAction(\n  neverPublisher: Publishers.Merge(Just<Void>(()), saveToDatabase)\n    .first()\n    .ignoreOutput()\n    .eraseToAnyPublisher())\nassert(!output.isEmpty && output.removeFirst() == "performNeverAction - completion")\nassert(!output.isEmpty && output.removeFirst() == "saving to database")\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(output.isEmpty)\nusleep(halfDelayMicroSeconds + sleepBufferMicroSeconds)\nassert(!output.isEmpty && output.removeFirst() == "saved to database")\nassert(output.isEmpty)\n\nprint("done")\n
Run Code Online (Sandbox Code Playgroud)\n

最后,作为Publisher运营商的解决方案如下:

\n
extension Publisher where Output == Never {\n  func asVoid() -> AnyPublisher<Void, Failure> {\n    self\n      .map { _ in () }\n      .append(Just(()).setFailureType(to: Failure.self))\n      .eraseToAnyPublisher()\n  }\n}\n\nextension Publisher where Output == Void {\n  func asNever(completeImmediately: Bool) -> AnyPublisher<Never, Failure> {\n    if completeImmediately {\n      return Just<Void>(())\n        .setFailureType(to: Failure.self)\n        .merge(with: self)\n        .first()\n        .ignoreOutput()\n        .eraseToAnyPublisher()\n    } else {\n      return self\n        .ignoreOutput()\n        .eraseToAnyPublisher()\n    }\n  }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n