Swift 合并嵌套发布者

Swift Combine Nested Publishers

我正在尝试结合 Swift 组合一个嵌套的发布者链,但我被难住了。我当前的代码开始在 .flatMap 行抛出错误,我不知道为什么。我一直在努力让它发挥作用,但我没有运气。

我想要完成的是下载 TrailerVideoResult 并对其进行解码,获取 TrailerVideo 对象数组,将其转换为 YouTube 网址数组,然后为每个 YouTube URL 获取 LPLinkMetadata .最终发布者应该 return 一组 LPLinkMetadata 对象。一切正常,直到 LPLinkMetadata 部分。

编辑:我更新了 loadTrailerLinks 函数。我原来忘了删除一些与这个例子无关的部分。

您将需要导入“LinkPresentation”。这是一个 Apple 框架,用于在您的应用程序中获取、提供和呈现丰富的链接。

最后一行 (eraseToAnyPublisher) 出现错误“表达式类型不明确,没有更多上下文”。

func loadTrailerLinks() -> AnyPublisher<[LPLinkMetadata], Error>{    
    return URLSession.shared.dataTaskPublisher(for: URL(string: "Doesn't matter")!)
        .tryMap() { element -> Data in
            guard let httpResponse = element.response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }
            return element.data
        }
        .decode(type: TrailerVideoResult.self, decoder: JSONDecoder(.convertFromSnakeCase))
        .compactMap{ [=11=].results }
        .map{ trailerVideoArray -> [TrailerVideo] in
            let youTubeTrailer = trailerVideoArray.filter({[=11=].site == "YouTube"})
            return youTubeTrailer
        }
        .map({ youTubeTrailer -> [URL] in
            return youTubeTrailer.compactMap{
                let urlString = "https://www.youtube.com/watch?v=\([=11=].key)"
                let url = URL(string: urlString)!
                return url
            }
        })
        .flatMap{ urls -> [AnyPublisher<LPLinkMetadata, Never>] in
            return urls.map{ url -> AnyPublisher <LPLinkMetadata, Never> in
                return self.getMetaData(url: url)
                    .map{ metadata -> LPLinkMetadata in
                        return metadata
                    }
                    .eraseToAnyPublisher()
            }
        }
        .eraseToAnyPublisher()
}
func fetchMetaData(url: URL) -> AnyPublisher <LPLinkMetadata, Never> {
    return Deferred {
        Future { promise in
            LPMetadataProvider().startFetchingMetadata(for: url) { (metadata, error) in
                promise(Result.success(metadata!))
            }
        }
    }.eraseToAnyPublisher()
}
struct TrailerVideoResult: Codable {
    let results : [TrailerVideo]
}
struct TrailerVideo: Codable {
    let key: String
    let site: String
}

您可以为此使用 Publishers.MergeMany and collect()

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

func loadTrailerLinks() -> AnyPublisher<[LPLinkMetadata], Error> {
  // Download data
  URLSession.shared.dataTaskPublisher(for: URL(string: "Doesn't matter")!)
    .tryMap() { element -> Data in
      guard let httpResponse = element.response as? HTTPURLResponse,
            httpResponse.statusCode == 200 else {
        throw URLError(.badServerResponse)
      }
      return element.data
    }
    .decode(type: TrailerVideoResult.self, decoder: decoder)
    // Convert the TrailerVideoResult to a MergeMany publisher, which merges the
    // [AnyPublisher<LPLinkMetadata, Never>] into a single publisher with output
    // type LPLinkMetadata
    .flatMap {
      Publishers.MergeMany(
        [=10=].results
          .filter { [=10=].site == "YouTube" }
          .compactMap { URL(string: "https://www.youtube.com/watch?v=\([=10=].key)") }
          .map(fetchMetaData)
      )
      // Change the error type from Never to Error
      .setFailureType(to: Error.self)
    }
    // Collect all the LPLinkMetadata and then publish a single result of
    // [LPLinkMetadata]
    .collect()
    .eraseToAnyPublisher()
}

将输入的值数组转换为结果数组有点棘手,每个结果都是通过发布者获得的。

如果顺序不重要,您可以 flatMap 输入 Publishers.Sequence 发布者,然后处理每个值,然后 .collect 它们:

.flatMap { urls in 
    urls.publisher // returns a Publishers.Sequence<URL, Never> publisher
}
.flatMap { url in
    self.getMetaData(url: url) // gets metadata publisher per for each url
}
.collect()

(我假设 getMetaData returns AnyPublisher<LPLinkMetadata, Never>

.collect 将收集所有发出的值,直到上游完成(但每个值可能不是按原始顺序到达)


如果您需要保留订单,还有更多工作要做。您可能需要发送原始索引,然后再对其进行排序。