Skip to content

Conversation

@soo127
Copy link
Collaborator

@soo127 soo127 commented Dec 17, 2025

학습 내용

영화진흥위원회 open api를 mvvm + combine으로 연결

핵심 코드

뷰컨트롤러가 load 되면, bind 함수를 호출하고,
영화 목록을 가져오면 테이블 뷰가 업데이트 됩니다.

final class MovieViewController: UIViewController {

    private let movieView = MovieView()
    private let viewModel = MovieViewModel()
    private var cancellables = Set<AnyCancellable>()

    private var dataSource: [MovieAPI.DailyBoxOffice] = []

    private func bind() {
        viewModel.movies
            .sink { [weak self] movies in
                self?.dataSource = movies
                self?.movieView.tableView.reloadData()
            }
            .store(in: &cancellables)

        viewModel.isLoading
            .sink { [weak self] isLoading in
                self?.movieView.updateLoading(isLoading: isLoading)
            }
            .store(in: &cancellables)
    }
}

뷰 모델에서는, load()를 통해 영화를 불러오고, send() 합니다.
우선 영화 목록을 불러오기 전까지는 로딩 상태를 유지하고, 영화 목록을 성공적으로 받아왔다면 이를 내보내고 로딩을 해제합니다.

final class MovieViewModel {
   
    let movies = PassthroughSubject<[MovieAPI.DailyBoxOffice], Never>()
    let isLoading = PassthroughSubject<Bool, Never>()

    func load() {
        isLoading.send(true)

        Task {
            do {
                let result = try await MovieAPI.fetch()
                movies.send(result)
            } catch {
                print("에러에러에러에러: \(error)")
            }
            isLoading.send(false)
        }
    }
    
}

네트워크 연결은 다음과 같이 구성했습니다.

enum APIHelper {

    static func fetch<T: Decodable>(url: URL?) async throws -> T {
        guard let url else {
            throw URLError(.badURL)
        }
        let (data, _) = try await URLSession.shared.data(from: url)
        let decoded = try JSONDecoder().decode(T.self, from: data)
        return decoded
    }

}

struct MovieAPI {

    static func fetch() async throws -> [DailyBoxOffice] {
        let fetched: BoxOfficeResponse = try await APIHelper.fetch(url: url)
        return fetched.boxOfficeResult.dailyBoxOfficeList
    }

}

extension MovieAPI {

    private static var url: URL? {
        let param = params
            .map { "\($0.key)=\($0.value)" }
            .joined(separator: "&")
        let url = Constants.baseURL + "?" + param
        return URL(string: url)
    }

    private static var params: [String: Any] {
        [
            "key": Constants.apiKey,
            "targetDt": "20251216",
            "repNationCd": "K"
        ]
    }

}

extension MovieAPI {

    private enum Constants {
        static let apiKey = "1e0e28f5603a7ff6e4038ebd37d0fdd7"
        static let baseURL = "https://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json"
    }

}

시연 영상

Simulator Screen Recording - iPhone 17 Pro - 2025-12-17 at 15 02 25

@soo127 soo127 self-assigned this Dec 17, 2025
Copy link
Collaborator

@y-eonee y-eonee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

깔끔하네요

extension MovieAPI {

private enum Constants {
static let apiKey = "1e0e28f5603a7ff6e4038ebd37d0fdd7"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 컨피그로 옮기면 좋을것같아요

final class MovieViewModel {

let movies = PassthroughSubject<[MovieAPI.DailyBoxOffice], Never>()
let isLoading = PassthroughSubject<Bool, Never>()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우 로딩중~~

Comment on lines +47 to +52
func updateLoading(isLoading: Bool) {
if isLoading {
loadingIndicator.startAnimating()
} else {
loadingIndicator.stopAnimating()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants