0

Here is my async function class:

class MoviesViewModel: ObservableObject {
@Published var topRated: [Movie] = []
@Published var popular: [Movie] = []
@Published var upcoming: [Movie] = []

func getUpcomingMovies() {
    if let movies = getMovies(path: "upcoming") {
        DispatchQueue.main.async {
            self.upcoming = movies
        }
    }
}

func getPopularMovies() {
    if let movies = getMovies(path: "popular") {
        DispatchQueue.main.async {
            self.popular = movies
        }
    }
}

func getTopRatedMovies() {
    DispatchQueue.main.async {
        if let movies = self.getMovies(path: "top_rated") {
            self.topRated = movies
        }
    }
}

func getMovies(path: String) -> [Movie]? {
    var movies: [Movie]?
    let urlString = "https://api.themoviedb.org/3/movie/\(path)?api_key=\(apiKey)&language=en-US&page=1"
    
    guard let url = URL(string: urlString) else { return [] }

    let session = URLSession.shared
    
    let dataTask = session.dataTask(with: url, completionHandler: { data, _, error in
        if error != nil {
            print(error)
        }
        do {
            if let safeData = data {
                let decodedData = try JSONDecoder().decode(NowPlaying.self, from: safeData)
               
                DispatchQueue.main.async {
                    movies = decodedData.results
                }
            }
        }
        catch {
            print(error)
        }
        
    })
    dataTask.resume()
    return movies
}

}

When I printed the movies in getMovies function, I can get movies from api without problem. However, UI does not update itself. I used DispatchQueue.main.async function but it did not solve my problem. What can I do in this situation?

themmfa
  • 499
  • 4
  • 15
  • 1
    `return movies` runs way before `movies = decodedData.results` this is mixi g asynchronous actions with synchronous. Do research on completion handles and/or async await – lorem ipsum Oct 12 '22 at 14:20

1 Answers1

1

dataTask works asynchronously. Your code returns nil even before the asynchronous task is going to start. You have to use a completion handler as described in Returning data from async call in Swift function.

I highly recommend to use async/await in this case. You get rid of a lot of boilerplate code and you don't need to care about dispatching threads.

@MainActor
class MoviesViewModel: ObservableObject {
    @Published var topRated: [Movie] = []
    @Published var popular: [Movie] = []
    @Published var upcoming: [Movie] = []
    
    func getUpcomingMovies() async throws {
        self.upcoming = try await getMovies(path: "upcoming")
    }
    
    func getPopularMovies() async throws  {
        self.popular = try await getMovies(path: "popular")
    }
    
    func getTopRatedMovies() async throws  {
        self.topRated = try await getMovies(path: "top_rated")
    }
    
    func getMovies(path: String) async throws -> [Movie] {
        let urlString = "https://api.themoviedb.org/3/movie/\(path)?api_key=\(apiKey)&language=en-US&page=1"
        
        guard let url = URL(string: urlString) else { throw URLError(.badURL) }
        
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(NowPlaying.self, from: data).results
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361