📌 TMDB API 데이터를 가져오는 3가지 방식
- Completion Handler (기존 방식)
- Combine (React 프로그래밍)
- async/await (Swift Concurrency)
📍 이전에 NetFlix를 클론 코딩했을 때는 Completion Handler 를 사용했습니다.
✅ 1. Completion Handler 방식 (기존 방식)
비동기 요청이 완료된 후 completion 블록을 실행하는 방식
func getTrendingMovies(completion: @escaping (Result<[Title], Error>) -> Void) {
let url = URL(string: "https://api.themoviedb.org/3/trending/movie/day")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [URLQueryItem(name: "language", value: "en-US")]
components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.timeoutInterval = 10
request.allHTTPHeaderFields = [
"accept": "application/json",
"Authorization": "Bearer \(Constants.API_KEY)"
]
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error occurred: \(error.localizedDescription)")
completion(.failure(APIError.failedToGetData))
return
}
guard let data = data else {
print("No data received")
completion(.failure(APIError.failedToGetData))
return
}
do {
let results = try JSONDecoder().decode(TrendingTitleResponse.self, from: data)
completion(.success(results.results))
} catch {
print("JSON Decoding error: \(error.localizedDescription)")
completion(.failure(APIError.failedToGetData))
}
}
task.resume()
}
🔹 장점
✅ 간단하고, 기존 코드와의 호환성이 좋음
✅ iOS 13 이하에서도 사용 가능
🔹 단점
⚠️ 콜백 중첩 문제 (Callback Hell) 발생 가능
⚠️ 코드 가독성이 떨어짐
✅ 2. Combine 방식
Combine을 사용하면 데이터 흐름을 스트림 형태로 관리
Future 또는 AnyPublisher를 사용하여 비동기적으로 데이터를 반환하는 방식
import Combine
class MovieService {
private var cancellables = Set<AnyCancellable>()
func getTrendingMovies() -> AnyPublisher<[Title], Error> {
let url = URL(string: "https://api.themoviedb.org/3/trending/movie/day")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [URLQueryItem(name: "language", value: "en-US")]
components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.timeoutInterval = 10
request.allHTTPHeaderFields = [
"accept": "application/json",
"Authorization": "Bearer \(Constants.API_KEY)"
]
return URLSession.shared.dataTaskPublisher(for: request)
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw APIError.failedToGetData
}
return data
}
.decode(type: TrendingTitleResponse.self, decoder: JSONDecoder())
.map { $0.results }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
🔹 사용 방법
let movieService = MovieService()
movieService.getTrendingMovies()
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Error: \(error)")
case .finished:
print("Successfully fetched movies")
}
}, receiveValue: { movies in
print("Movies: \(movies)")
})
.store(in: &cancellables)
🔹 장점
✅ 반응형 프로그래밍 가능 (데이터가 변경될 때 UI 업데이트 가능)
✅ 콜백 중첩 문제 해결
✅ 데이터 스트림을 쉽게 관리할 수 있음
🔹 단점
⚠️ iOS 13 이상에서만 사용 가능
⚠️ Combine을 잘 이해해야 사용 가능
⚠️ 메모리 관리 필요 (store(in: &cancellables))
✅ 3. async/await 방식 (Swift Concurrency)
iOS 15 이상에서는 async/await을 사용하여 가독성이 뛰어난 비동기 처리가 가능
🔹 코드
class NetworkManager {
static let shared = NetworkManager()
func getTrendingMovies() async throws -> [MovieResult] {
let url = URL(string: "\(Constants.baseURL)trending/movie/week")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [
URLQueryItem(name: "language", value: "ko-KR"),
]
components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.timeoutInterval = 10
request.allHTTPHeaderFields = [
"accept": "application/json",
"Authorization": "Bearer \(Constants.API_KEY)"
]
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else { throw APIError.failedToGetData }
let results = try JSONDecoder().decode(MovieWelcome.self, from: data)
return results.results
}
}
🔹 사용 방법
Task {
do {
let movies = try await getTrendingMovies()
print("Movies: \(movies)")
} catch {
print("Error: \(error)")
}
}
🔹 장점
✅ 코드 가독성이 뛰어남
✅ 비동기 처리가 간결해짐
✅ 콜백 중첩 문제 해결
🔹 단점
⚠️ iOS 15 이상에서만 사용 가능
⚠️ 기존 코드와 호환성이 떨어질 수 있음
🚀 어떤 방식을 선택해야 할까?
✅ iOS 13 이하까지 지원해야 한다면 → Completion Handler
✅ Combine을 이미 사용하고 있다면 → Combine 방식
✅ iOS 15 이상에서 개발 중이라면 → async/await
📍 async / await 방식 채택 (한 번도 사용해보지 않았기 때문이고, 가독성이 좋아서 채택)
💻 에러 처리 강화 관련 APIERROR 확장
🔷 기존
// MARK: - ERROR
enum APIError: Error {
case failedToGetData
}
🔷 개선
enum APIError: Error {
case invalidURL // URL이 잘못된 경우 (강제 언래핑 방지)
case requestFailed(statusCode: Int) // HTTP 상태 코드가 200이 아닐 때
case decodingFailed(Error) // JSON 디코딩 실패 시
case networkError(Error) // 네트워크 에러 발생 시
case unknownError // 알 수 없는 에러 발생 시
}
'Project > MovieClip' 카테고리의 다른 글
genre_ids 의 Int 타입의 배열에 맞는 genre 찾기 (0) | 2025.02.06 |
---|---|
테이블 섹션 별 데이터 모델 사용 (0) | 2025.02.06 |
에러 발생 - 데이터 모델 누락 (0) | 2025.02.05 |
테이블과 테이블 헤더뷰 제약조건 구현하기, 글자색 따로 주기 (0) | 2025.02.05 |
앱 개요 (0) | 2025.02.04 |