【问题标题】:Chaining swift combine publishers and receiving each result链接 swift combine 发布者并接收每个结果
【发布时间】:2020-08-18 14:01:58
【问题描述】:

在下面的示例中,我发出一个网络请求来加载不同类型的电影,然后使用它来加载所有电影。接收器只返回电影结果。我怎样才能同时接收流派和电影?

struct Genre: Codable, Identifiable{
    let id: Int
    let name: String
    var movies: [Movie]?
}

struct Movie: Codable, Hashable, Identifiable {
    let title: String
    let id: Int
    let posterPath: String?
    let backdropPath : String?
    var tagline: String?
}

loadGenres() is AnyPublisher<[Genre], Error> 
fetchMoviesIn() is AnyPublisher<[Movie], Error>

class GenresViewModel: ObservableObject{
    @Published var genres = [Genre]()
    @Published var movies = [Movie]()
    var requests = Set<AnyCancellable>()
    
    init(){
        NetworkManager.shared.loadGenres()
            .flatMap{ genres in
                genres.publisher.flatMap{ genre in
                    NetworkManager.shared.fetchMoviesIn(genre)
                }
            }
            .collect()
            .retry(1)
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { completion in
                switch completion{
                case .finished:
                    print("Finished loading all movies in every genre")
                case .failure(let error):
                    print("Error: \(error)")
                }
            }, receiveValue: { [self] values in
                let allMovies = values.joined()
                self.movies = allMovies.map{$0}
            })
            .store(in: &self.requests)
    }
}

【问题讨论】:

  • loadGenres()fetchMoviesIn 方法的返回类型是什么?
  • AnyPublisher 和 AnyPublisher
  • @RichardWitherspoon,你想如何收集最终结果?例如:[(Genre, [Movie])] 是一个类型到电影映射的数组,或者只是独立的数组:([Genre], [Movie])
  • @NewDev 更喜欢第一个,但如果你可以为两者提供示例,那么我理解这将是惊人的差异。

标签: swift combine


【解决方案1】:

取决于您希望如何收集流派和电影。

例如,您想要一个类型和该类型的电影列表吗?结果可能是(Genre, [Movies]) 的数组。

NetworkManager.shared.loadGenres()
   .flatMap { genres in
       genres.publisher.setFailureType(to: Error.self)
   }
   .flatMap { genre in
       NetworkManager.shared.fetchMoviesIn(genre)
          .map { movies in (genre, movies) } 
   }
   .collect()

或者,如果您想要一个 (Genre, Movie) 元组数组,那么这是一种类似的方法,但需要额外的 .flatMap 级别来获取单个电影

NetworkManager.shared.loadGenres()
   .flatMap { genres in
       genres.publisher.setFailureType(to: Error.self)
   }
   .flatMap { genre in
       NetworkManager.shared.fetchMoviesIn(genre)
          .flatMap { movies in
              movies.publisher.setFailureType(to: Error.self)
          }
          .map { movie in (genre, movie) }
   }
   .collect()

要回答您的评论问题,您想返回更新后的Genre,您可以返回它而不是返回一个元组。请记住,由于Genre 是一个结构,您需要创建对象的变量副本(flatMap 闭包中可用的genre 是一个常量),更新副本,然后返回:

NetworkManager.shared.loadGenres()
   .flatMap { genres in
       genres.publisher.setFailureType(to: Error.self)
   }
   .flatMap { genre in
       NetworkManager.shared.fetchMoviesIn(genre)
          .map { movies -> Genre in
             var genreCopy = genre
             genreCopy.movies = movies
             return genreCopy
          }
   }
   .collect()

【讨论】:

  • 多么棒的答案!太棒了。对于我的用例,第一种方法是最好的。 Genre 还有一个 movies 属性 ([Movie]?) 。我将如何获取完成中返回的值并将它们填充到类的发布属性中?
  • 我在您的回答中添加了我的解决方案,但我觉得还有更好的方法。
  • @RichardWitherspoon,感谢您的编辑,但我认为它不应该出现在答案中,因为它会使水更加混乱。再次......一切都取决于您希望发布者链返回给订阅者的内容。如果你的元组没问题,那么你可以在.sink 中处理它们。如果您希望订阅者接收“已完成的”Genre 对象,则不要创建元组,而是使用 movies 属性创建副本/更新流派对象,然后返回。
  • 我删除了我的编辑。你有没有机会用你的解释来更新它?抱歉,Combine 仍然在为我弯曲。
  • 所以,如果Genre 有一个属性.movies,你可以这样做:.map { movies in genre.movies = movies; return genre },而不是.map { movies in (genre, movies) }。或者,您可以按照您在编辑中的建议进行操作 - 这取决于您并取决于您希望如何分解代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 2021-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多