映射Combine数组时如何避免嵌套闭包?

How to avoid nesting closures when mapping Combine arrays?

我有一个字符串数组,我想将其映射到不同的对象中。我正在使用 map 将数组转换为另一个数组,但它非常嵌套:

$favorites
    .map { articleIDs in
        articleIDs.compactMap { id in
            state.articles.first { [=10=].id == id }
        }
    }
    .assign(to: \Self.favorites, on: self)
    .store(in: &cancellable)

是否有 shorthand 将转换应用于每个单独的项目?我本来想这样做,但没有成功:

state.$favorites
    .mapEach { id in state.articles.first { [=11=].id == id } }
    .assign(to: \Self.favorites, on: self)
    .store(in: &cancellable)

这是一种方法,您:

  1. 使用 Publishers.Sequence
  2. 将单个数组转换为单个元素的管道
  3. 单独处理元素。
  4. 使用 collect 运算符将元素转换回数组。

这是一个您可以在 Xcode 操场上 运行 设计的示例:

import Combine
import UIKit

class MyStore: ObservableObject {
  @Published var favorites: [(id: Int, title: String)] = []
  let articles = [
    (id: 22, title: "foo"),
    (id: 5, title: "bar"),
    (id: 13, title: "baz"),
  ]
  var cancellable: Set<AnyCancellable> = []

  func addFavorites(favorites: AnyPublisher<[Int], Never>) {
    let articles = self.articles
    favorites
      .flatMap(Publishers.Sequence.init)
      .compactMap { fav in articles.first { fav == [=10=].id }}
      .collect()
      .assign(to: \.favorites, on: self)
      .store(in: &cancellable)
  }
}

let store = MyStore()
store.addFavorites(favorites: Just([22, 13]).eraseToAnyPublisher())
print(store.favorites)
// => [(id: 22, title: "foo"), (id: 13, title: "baz")]

...但是我怀疑这实际上不是您想要的。一个更优雅的解决方案可能是创建一个定制的 compactMapEach 运算符。这是它的样子:

extension Publisher {
  public func compactMapEach<T, U>(_ transform: @escaping (T) -> U?)
    -> Publishers.Map<Self, [U]>
    where Output == [T]
  {
    return map { [=11=].compactMap(transform) }
  }
}