使用TMDB API获取随机电影时总页数无法获取求助
问题:TMDB API无法获取总页数
我基于TMDB API开发随机电影获取功能,核心逻辑是先获取符合筛选条件的总页数,再随机选页码拉取电影,但目前无法成功获取总页数,相关代码如下:
1. 核心业务逻辑函数
performTask(获取总页数并触发后续流程)
func performTask(numOption: Int, selectedGenresViewModel: SelectedGenresViewModel, selectedProviderViewModel: SelectedProviderViewModel, completion: @escaping ([Int]) -> Void) { let genreIDs = selectedGenresViewModel.selectedGenres.map { $0.id } let providerIDs = selectedProviderViewModel.selectedProvider.map { $0.provider_id } RandomMovieStore.shared.discoverMovies(page: 1, genres: genreIDs.map(String.init).joined(separator: ","), providers: providerIDs.map(String.init).joined(separator: ",")) { result in switch result { case .success(let (_, totalPages)): let pageNumbers = getRandomPageNumbers(numOption: numOption, totalPages: totalPages) applyFiltersAndFindMovieIDs(pages: pageNumbers, genres: genreIDs, providers: providerIDs) { ids in completion(ids) print("total pages are: \(totalPages)") } case .failure(let error): print("API call failed with error: \(error)") completion([]) } }
getRandomPageNumbers(生成随机页码)
func getRandomPageNumbers(numOption: Int, totalPages: Int) -> [Int] { let randomNumbers = Array(1...totalPages).shuffled().prefix(numOption) return Array(randomNumbers) }
applyFiltersAndFindMovieIDs(批量拉取随机页电影)
func applyFiltersAndFindMovieIDs(pages: [Int], genres: [Int], providers: [Int], completion: @escaping ([Int]) -> Void) { var movieIDs = [Int]() let group = DispatchGroup() for page in pages { group.enter() let genreIDs = genres.map(String.init).joined(separator: ",") let providerIDs = providers.map(String.init).joined(separator: ",") RandomMovieStore.shared.discoverMovies(page: page, genres: genreIDs, providers: providerIDs) { result in switch result { case .success(let (response, _)): if let randomMovie = response.results?.randomElement() { let randomMovieID = randomMovie.id print("Random movie ID:", randomMovieID) movieIDs.append(randomMovieID) } case .failure(let error): print("API call failed with error: \(error)") } group.leave() } } group.notify(queue: .main) { print("All API calls completed") completion(movieIDs) } }
2. 按钮触发逻辑
Button(action: { Task { performTask( numOption: numOption, selectedGenresViewModel: selectedGenresViewModel, selectedProviderViewModel: selectedProviderViewModel ) { movieIDs in var fetchedMovies = [Movie]() let dispatchGroup = DispatchGroup() // 获取电影ID以提取相关信息 for id in movieIDs { dispatchGroup.enter() print("Fetching movie with ID: \(id)") MovieStore.shared.fetchMovie(id: id) { result in switch result { case .success(let movie): fetchedMovies.append(movie) case .failure(let error): print(error.localizedDescription) } dispatchGroup.leave() } } dispatchGroup.notify(queue: .main) { self.filteredMovies = fetchedMovies self.movieTitles = fetchedMovies.map({ $0.title }) print(self.movieTitles) } } } })
3. API调用核心实现
func discoverMovies(page: Int, genres: String, providers: String, completion: @escaping (Result<(RandomMovieResponse, Int?), MovieError>) -> ()) { guard let url = URL(string: "https://api.themoviedb.org/3/discover/movie?api_key=\(apiKey)&include_adult=false&page=\(page)&with_genres=\(genres)&watch_region=NL&vote_average.ite=1&with_watch_providers=\(providers)") else { completion(.failure(.invalidEndpoint)) return } self.loadURLAndDecode(url: url) { [weak self] (result: Result<RandomMovieResponse, MovieError>) in guard let self = self else { return } switch result { case .success(let response): self.totalPages = response.total_pages completion(.success((response, response.total_pages))) case .failure(let error): completion(.failure(error)) } } print(url) } private func loadURLAndDecode<D: Decodable>(url: URL, params: [String: String]? = nil, completion: @escaping (Result<D, MovieError>) -> ()) { guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { completion(.failure(.invalidEndpoint)) return } var queryItems = [URLQueryItem(name: "api_key", value: apiKey)] if let params = params { queryItems.append(contentsOf: params.map { URLQueryItem(name: $0.key, value: $0.value) }) } urlComponents.queryItems = queryItems guard let finalURL = urlComponents.url else { completion(.failure(.invalidEndpoint)) return } urlSession.dataTask(with: finalURL) { [weak self] (data, response, error) in guard let self = self else { return } if error != nil { self.executeCompletionHandlerInMainThread(with: .failure(.apiError), completion: completion) return } guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else { self.executeCompletionHandlerInMainThread(with: .failure(.invalidResponse), completion: completion) return } guard let data = data else { self.executeCompletionHandlerInMainThread(with: .failure(.noData), completion: completion) return } do { let decodedResponse = try self.jsonDecoder.decode(D.self, from: data) self.executeCompletionHandlerInMainThread(with: .success(decodedResponse), completion: completion) } catch { self.executeCompletionHandlerInMainThread(with: .failure(.serializationError), completion: completion) } }.resume() } private func executeCompletionHandlerInMainThread<D: Decodable>(with result: Result<D, MovieError>, completion: @escaping (Result<D, MovieError>) -> ()) { DispatchQueue.main.async { completion(result) } } } struct RandomMovieResponse: Decodable { let page: Int? let total_results: Int? let total_pages: Int? let results: [Movie]? }
问题排查与解决方案
1. URL参数拼写错误
discoverMovies里的URL包含无效参数vote_average.ite=1,这是拼写错误,正确的参数名应该是vote_average.gte(大于等于)或vote_average.lte(小于等于),无效参数会导致TMDB API返回错误,无法解析出total_pages。
修正后的URL:
guard let url = URL(string: "https://api.themoviedb.org/3/discover/movie?api_key=\(apiKey)&include_adult=false&page=\(page)&with_genres=\(genres)&watch_region=NL&vote_average.gte=1&with_watch_providers=\(providers)") else { completion(.failure(.invalidEndpoint)) return }
2. loadURLAndDecode覆盖原URL参数
当前loadURLAndDecode函数会丢弃原URL中的所有query参数,重新创建只包含api_key和传入params的queryItems,导致原URL里的include_adult、page等关键参数丢失,API请求完全不符合预期。
修改loadURLAndDecode,保留原URL的query参数:
private func loadURLAndDecode<D: Decodable>(url: URL, params: [String: String]? = nil, completion: @escaping (Result<D, MovieError>) -> ()) { guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { completion(.failure(.invalidEndpoint)) return } // 保留原URL的queryItems,为空则初始化 var queryItems = urlComponents.queryItems ?? [] // 追加传入的参数 if let params = params { queryItems.append(contentsOf: params.map { URLQueryItem(name: $0.key, value: $0.value) }) } // 确保api_key存在(避免原URL遗漏) if !queryItems.contains(where: { $0.name == "api_key" }) { queryItems.append(URLQueryItem(name: "api_key", value: apiKey)) } urlComponents.queryItems = queryItems guard let finalURL = urlComponents.url else { completion(.failure(.invalidEndpoint)) return } // 后续dataTask逻辑不变 }
3. 处理total_pages为nil的情况
RandomMovieResponse中的total_pages是可选类型,若API返回缺失或解析失败会导致nil,进而触发后续逻辑错误。在performTask中增加判断:
case .success(let (_, totalPages)): guard let validTotalPages = totalPages, validTotalPages > 0 else { print("Invalid total pages count") completion([]) return } let pageNumbers = getRandomPageNumbers(numOption: numOption, totalPages: validTotalPages) // 后续流程
4. 检查API权限与地区限制
确认你的TMDB API Key有权限访问discover接口,且watch_region=NL设置的地区有可用内容。若地区无匹配内容,API会返回total_pages=0,需提示用户调整筛选条件。
内容的提问来源于stack exchange,提问作者Joost van Grieken
相关产品推荐
相关产品推荐

