저번에 UICollectionView의 Compositional Layout을 공부할 때 만들었던 영화앱의 API 통신 부분을 공부할 겸 변경하게 되었다.
영화진흥원 API를 사용해 일간, 주간 박스 오피스 리스트를 가져오고, KMDB API를 사용해 영화 상세정보를 받아와 포스터를 띄웠다.
///BoxOffice 일간, 주간 정보 모두 반환 func searchBoxOfficeInfo(dateRange: DateRange) async throws -> [MovieInfo] { let url = APIInfo.boxOfficeHost + dateRange.rawValue var dataTask: DataTask<BoxOfficeJSON> switch dateRange { case .daily: let param = BoxOfficeRequestModel(key: getAPIKey(hostName: .kobis), targetDt: Date().dailyDate) dataTask = AF.request(url, method: .get, parameters: param).serializingDecodable(BoxOfficeJSON.self) case .weekly: let param = BoxOfficeRequestModel(key: getAPIKey(hostName: .kobis), targetDt: Date().weeklyDate) dataTask = AF.request(url, method: .get, parameters: param).serializingDecodable(BoxOfficeJSON.self) } switch await dataTask.result { case .success(let data): return dateRange == .daily ? data.boxOfficeResult.dailyBoxOfficeList! : data.boxOfficeResult.weeklyBoxOfficeList! case .failure(let error): print(error.localizedDescription) throw error } } ///영화 상세 정보 func searchMovieInfo(title: String, releaseDate: String?) async throws -> Movie? { let url = APIInfo.movieDetailHost var date: String { if let temp = releaseDate { return temp.replacingOccurrences(of: "-", with: "") } else { return "" } } let param: Parameters = ["ServiceKey": getAPIKey(hostName: .kmdb), "collection": "kmdb_new2", "detail": "Y", "title": title, "releaseDts": date] let dataTask = AF.request(url, method: .get, parameters: param).serializingDecodable(MovieDetailJSON.self) switch await dataTask.result { case .success(let data): guard let data = data.data[0].result else { print("가져오기 실패 [\(title)] : \(data.data)") return nil } return data[0] case .failure(let error): throw error } }
Request 함수는 Alamofire를 사용해 구현했다. Alamofire에서도 async/await를 지원하는 함수가 있어 그걸 그대로 사용하니 간단했다.
이전에 열심히 escaping closure를 사용했을 때 보다 코드도 간결하고 깔끔해졌다.
실제로 데이터를 사용하는 부분의 구현은 동시성, 병렬처리가 가능하다는 장점을 살리고 싶어, async let syntax를 일간, 주간 박스오피스를 가져올 때 사용해보고, 박스오피스 영화들의 정보를 가져올 땐 task group을 사용해 구현했다.
private func getDataAsync() { Task { do { async let daily = APIManager().searchBoxOfficeInfo(dateRange: .daily) async let weekly = APIManager().searchBoxOfficeInfo(dateRange: .weekly) let movies = try await [daily, weekly] //BoxOffice 일간 주간 리스트 dailyList = movies[0] weeklyList = movies[1] //boxOfficeInfo에서 영화이름을 Key로 포스터를 가져오거나 상세 정보를 표시. boxOfficeInfo = try await loadMovieDetail(movies: dailyList + weeklyList) collectionView.reloadData() } catch { fatalError(error.localizedDescription) } } } private func loadMovieDetail(movies: [MovieInfo]) async throws -> [String: Movie] { var temp: [String: Movie] = [:] try await withThrowingTaskGroup(of: (String, Movie?).self) { group in for movie in (dailyList + weeklyList) { group.addTask { return try await (movie.movieNm, APIManager().searchMovieInfo(title: movie.movieNm, releaseDate: movie.openDt)) } } for try await (name, movie) in group { guard let movie else { continue } temp[name] = movie } } return temp }
Swift Concurrency 영상에서 처럼 썸네일을 가져오는 코드를 비슷하게 구현했다. 정확히는 영화의 상세정보를 가져오는 것이지만, async-let과 달리 많고, 가변적인 상황에서는 taskgroup으로 처리하는게 정말 간편하다.
또한 ViewController에서 작업하면 Task가 MainActor를 따르기 때문에 메인스레드에 따로 UI를 조작하는 번거로움도 사라지고 이 부분이 코드가 가장 간결해졌다.
아쉽게도 KMDB에서 제공하는 영화상세정보 API가 영화이름과 개봉일로 검색해도 제대로 검색이 안되어서 이미지가 로드가 안 된 것들이 많다.
재개봉의 경우 "|"를 구분자로 사용하는데 검색시 대응이 안되는 문제도 있고 이번에 개봉한 "웅남이"도 API에서 이름으로만 검색해도 안나오는 알 수 없는 문제가 있다... 그냥 그런대로 써야겠다.

원래는 MVVM + RxSwift로 변경하려고 했던 프로젝트였지만, 이걸 적용해보게 됐다.. Rx는 왜 어려울까