0

I am trying to fetch data from themoviedb page. I made a class called MovieService API to retrieve movies by category, single movie by Id:

class MovieServiceAPI {

    public static let shared = MovieServiceAPI()

    private init() {}
    private let urlSession = URLSession.shared
    private let baseURL = URL(string: "https://api.themoviedb.org/3/movie/")!
    private let apiKey = "?api_key=a415cfdc3dc928bd4649d310e90939e6"

    private let jsonDecoder: JSONDecoder = {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy"
        jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
        return jsonDecoder
    }()

    enum Endpoint: String, CaseIterable {

        case nowPlaying = "now_playing"
        case upcoming
        case popular
        case topRated = "top_rated"
    }

    public enum APIServiceError: Error {
        case apiError
        case invalidEndpoint
        case invalidResponse
        case noData
        case decodeError
    }

    private func fetchResources<T: Decodable>(url: URL, completion: @escaping (Result<T, APIServiceError>) -> Void) {
        guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
            completion(.failure(.invalidEndpoint))
            return
        }
        let queryItems = [URLQueryItem(name: "api_key", value: apiKey)]
        urlComponents.queryItems = queryItems
        guard let url = urlComponents.url else {
            completion(.failure(.invalidEndpoint))
            return
        }

        urlSession.dataTask(with: url) { (result) in
            switch result {
            case .success(let (response, data)):
                guard let statusCode = (response as? HTTPURLResponse)?.statusCode, 200..<299 ~= statusCode else {
                    completion(.failure(.invalidResponse))
                    return
                }
                do {
                    let values = try self.jsonDecoder.decode(T.self, from: data)
                    completion(.success(values))
                } catch {
                    completion(.failure(.decodeError))
                    print(error)
                }
            case .failure(let error):
                completion(.failure(.apiError))
            }
        }.resume()
    }

    public func fetchMovies(from endpoint: Endpoint, result: @escaping (Result<NowPlayingResponse, APIServiceError>) -> Void) {
        let movieURL = baseURL
            .appendingPathComponent("movie")
            .appendingPathComponent(endpoint.rawValue)
        fetchResources(url: movieURL, completion: result)
    }

    public func fetchMovie(movieId: Int, result: @escaping (Result<NowPlayingResults, APIServiceError>) -> Void) {

        let movieURL = baseURL
            .appendingPathComponent("movie")
            .appendingPathComponent(String(movieId))
        fetchResources(url: movieURL, completion: result)
    }
}

I called fetchMovies function from MovieServiceAPI in my HomeViewController :

private extension HomeViewController {
    func getMovies() {
        MovieServiceAPI.shared.fetchMovies(from: .nowPlaying) { (result: Result<NowPlayingResponse, MovieServiceAPI.APIServiceError>) in
            switch result {
            case .success(let movieResponse):
                self.movieList = movieResponse.results
            case .failure(let error):
                print(error.localizedDescription)
            }

        }
        print(movieList)
    }
}

But the problem is that values are not saved in movieList array when it exit switch case, but when I called print(self.movieList) in switch, there are values in array.

Witek Bobrowski
  • 3,749
  • 1
  • 20
  • 34
  • `elf.movieList = movieResponse.results` => `print("Completion called"); self.movieList = movieResponse.results` and `print(movieList)` => `print("After: \(movieList)")`. Which modified/new print appears first in console? Which one do you think it should be? – Larme Jul 02 '21 at 16:11
  • Just to be clear, you call `print(movieList)` outside of the switch statement, not within it. In fact, you call it before the API has had a chance to return a result. What happens when you print inside the `case .success(let movieResponse)` case? What is the Swift type of the `movieList` array? And what is actual type you're getting from `NowPlayingResponse`? – trndjc Jul 02 '21 at 16:14
  • It print data normally when it is in case. Problem is that the array temporarily containts data, and when switch statement is done, array return to empty. – Marko Markovic Jul 02 '21 at 16:52
  • Can you show how you use the getMovies function? This looks like you are trying to access `self.movieList` before there is a response from the server. – Yakume Jul 02 '21 at 17:30
  • This is one of the more common questions iOS/Swift questions, and indicates a basic misunderstanding of how async functions with completion handlers work. Analogy: You're cooking dinner and realize you are out of eggs. You give your kid some money and tell them to go buy eggs. You don't then immediately look in the fridge for eggs. You do something else and wait for your kid to say "I'm back with the eggs." Your print statement is like that. It is outside of the completion handler, and will run before the network request has run. – Duncan C Jul 02 '21 at 19:29

0 Answers0