Chaining swift menggabungkan penerbit dan menerima setiap hasil

Aug 18 2020

Pada contoh di bawah, saya membuat permintaan jaringan untuk memuat genre film yang berbeda, lalu menggunakannya untuk memuat semua film. Wastafel hanya mengembalikan hasil film. Bagaimana saya bisa menerima genre dan film?

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

Jawaban

2 NewDev Aug 18 2020 at 17:03

Tergantung bagaimana Anda ingin mengumpulkan genre dan film.

Misalnya, apakah Anda menginginkan genre dan daftar film dalam genre tersebut? Hasilnya bisa berupa array (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()

Atau, jika Anda menginginkan serangkaian (Genre, Movie)tupel, maka itu pendekatan yang serupa, tetapi dengan tingkat tambahan .flatMapuntuk mendapatkan film individual

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

Untuk menjawab pertanyaan komentar Anda, Anda ingin mengembalikan yang telah diperbarui Genre, Anda dapat mengembalikannya daripada mengembalikan tupel. Ingatlah bahwa karena Genreadalah struct, Anda perlu membuat salinan variabel dari objek (yang genretersedia di flatMapclosure adalah konstanta), perbarui salinannya, dan kembalikan:

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