Encadeando o Swift combina editores e recebendo cada resultado

Aug 18 2020

No exemplo abaixo, estou fazendo uma solicitação de rede para carregar gêneros de filmes diferentes e, em seguida, usar isso para carregar todos os filmes. A pia retorna apenas os resultados do filme. Como posso receber os gêneros e filmes?

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)
    }
}

Respostas

2 NewDev Aug 18 2020 at 17:03

Depende de como você deseja coletar gêneros e filmes.

Por exemplo, você quer um gênero e uma lista de filmes desse gênero? O resultado pode ser uma matriz de (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()

Ou, se você quiser uma matriz de (Genre, Movie)tuplas, é uma abordagem semelhante, mas com um nível adicional de .flatMappara obter filmes individuais

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()

Para responder a sua pergunta de comentário, você deseja retornar o atualizado Genre, você pode retornar isso em vez de retornar uma tupla. Lembre-se de que, como Genreé uma estrutura, você precisa criar uma cópia variável do objeto (o genredisponível no flatMapfechamento é uma constante), atualizar a cópia e retornar:

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()